From cd7d7284e5288c655dd91d094d2722f96fc0d4a3 Mon Sep 17 00:00:00 2001 From: Ken Celenza Date: Mon, 24 Jul 2023 22:34:30 -0400 Subject: [PATCH 01/21] Add mkdocs link to 1.5 release notes (#323) --- mkdocs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/mkdocs.yml b/mkdocs.yml index 981c82da..c2c2e943 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -116,6 +116,7 @@ nav: - v1.2: "admin/release_notes/version_1.2.md" - v1.3: "admin/release_notes/version_1.3.md" - v1.4: "admin/release_notes/version_1.4.md" + - v1.5: "admin/release_notes/version_1.5.md" - Developer Guide: - Extending the Library: "dev/extending.md" - Contributing to the Library: "dev/contributing.md" From 0b01fcfac48b62453ddf583fb7db76d33bbff222 Mon Sep 17 00:00:00 2001 From: Ken Celenza Date: Fri, 4 Aug 2023 16:18:16 -0400 Subject: [PATCH 02/21] Add classy docs to view config parser classes (#325) --- docs/dev/classy/parser.md | 4 ++++ docs/requirements.txt | 1 + mkdocs.yml | 8 +++++++ poetry.lock | 47 ++++++++++++++++++++++++++++++++++++++- pyproject.toml | 1 + 5 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 docs/dev/classy/parser.md diff --git a/docs/dev/classy/parser.md b/docs/dev/classy/parser.md new file mode 100644 index 00000000..1709fa60 --- /dev/null +++ b/docs/dev/classy/parser.md @@ -0,0 +1,4 @@ +--- +classy_dotted_path: netutils.config.parser.BaseConfigParser +--- +# Classy Doc diff --git a/docs/requirements.txt b/docs/requirements.txt index 8e223b74..a8f5d07b 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,6 +1,7 @@ mkdocs==1.4.3 # Material for MkDocs theme mkdocs-material==8.5.8 +mkdocs-python-classy==0.1.2 # Render custom markdown for version added/changed/remove notes mkdocs-version-annotations==1.0.0 # Automatic documentation from sources, for MkDocs diff --git a/mkdocs.yml b/mkdocs.yml index c2c2e943..3e8d9909 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -82,6 +82,12 @@ markdown_extensions: plugins: - "search" - "mkdocs-version-annotations" + - "mkdocs-python-classy": + classy_strategy: "subclass" + classy_modules: + - "netutils.config.parser" + classy_libraries: + - "netutils" - "mkdocstrings": default_handler: "python" handlers: @@ -144,3 +150,5 @@ nav: - Time: "dev/code_reference/time.md" - Utils: "dev/code_reference/utils.md" - VLAN: "dev/code_reference/vlan.md" + - Classy Docs: + - Parser: "dev/classy/parser.md" diff --git a/poetry.lock b/poetry.lock index 972da29d..8b3f4db8 100644 --- a/poetry.lock +++ b/poetry.lock @@ -30,6 +30,21 @@ wrapt = [ {version = ">=1.14,<2", 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.12.1" @@ -1065,6 +1080,22 @@ files = [ {file = "mkdocs_material_extensions-1.1.1.tar.gz", hash = "sha256:9c003da71e2cc2493d910237448c672e00cefc800d3d6ae93d2fc69979e3bd93"}, ] +[[package]] +name = "mkdocs-python-classy" +version = "0.1.2" +description = "Mkdocs plugin to view Python Classes." +optional = false +python-versions = ">=3.8,<4.0" +files = [ + {file = "mkdocs_python_classy-0.1.2-py3-none-any.whl", hash = "sha256:73843815a6aec1ce2088177a5920b5df7f3d208291e21b82dbd1c40271adb42f"}, + {file = "mkdocs_python_classy-0.1.2.tar.gz", hash = "sha256:89e7053e2a30351a068108e6a337479d77db8c1f3e74f3e3507a1322ccb84683"}, +] + +[package.dependencies] +astunparse = {version = ">=1.6.3,<2.0.0", markers = "python_version < \"3.9\""} +mkdocs = ">=1.4.0,<2.0.0" +pymdown-extensions = ">=6.3" + [[package]] name = "mkdocs-version-annotations" version = "1.0.0" @@ -2078,6 +2109,20 @@ files = [ [package.extras] watchmedo = ["PyYAML (>=3.10)"] +[[package]] +name = "wheel" +version = "0.41.0" +description = "A built-package format for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "wheel-0.41.0-py3-none-any.whl", hash = "sha256:7e9be3bbd0078f6147d82ed9ed957e323e7708f57e134743d2edef3a7b7972a9"}, + {file = "wheel-0.41.0.tar.gz", hash = "sha256:55a0f0a5a84869bce5ba775abfd9c462e3a6b1b7b7ec69d72c0b83d673a5114d"}, +] + +[package.extras] +test = ["pytest (>=6.0.0)", "setuptools (>=65)"] + [[package]] name = "wrapt" version = "1.15.0" @@ -2214,4 +2259,4 @@ optionals = ["napalm"] [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "31acbdd2552dcd4f4f421b8ec8bc0ba4c39aa2bf2b3b03a557ade07c64478520" +content-hash = "be93681a86ecec82804fec9ab1dcd4f6c8c81cb6b455cd6694ac4d4377239232" diff --git a/pyproject.toml b/pyproject.toml index 99b1a0e9..fb36b988 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,6 +50,7 @@ mkdocs-material = "8.5.8" mkdocstrings = "0.22.0" mkdocstrings-python = "1.1.2" mkdocs-version-annotations = "1.0.0" +mkdocs-python-classy = "0.1.2" [tool.black] line-length = 120 From e0be339280fb18230017dcb91d72a6a1e8538549 Mon Sep 17 00:00:00 2001 From: Ken Celenza Date: Fri, 4 Aug 2023 19:13:40 -0400 Subject: [PATCH 03/21] Add int_to_asn function (#332) * Add int_to_asn function * add tests --- docs/user/include_jinja_list.md | 1 + netutils/asn.py | 28 ++++++++++++++++++++++++++++ netutils/utils.py | 1 + tests/unit/test_asn.py | 32 ++++++++++++++++++++++++++++++++ 4 files changed, 62 insertions(+) diff --git a/docs/user/include_jinja_list.md b/docs/user/include_jinja_list.md index 4be27d36..a2901448 100644 --- a/docs/user/include_jinja_list.md +++ b/docs/user/include_jinja_list.md @@ -1,6 +1,7 @@ | Filter name | Function | | ---------- | ------ | | asn_to_int | netutils.asn.asn_to_int | +| int_to_asn | netutils.asn.int_to_asn | | bits_to_name | netutils.bandwidth.bits_to_name | | bytes_to_name | netutils.bandwidth.bytes_to_name | | name_to_bits | netutils.bandwidth.name_to_bits | diff --git a/netutils/asn.py b/netutils/asn.py index 04323b4e..f3833908 100644 --- a/netutils/asn.py +++ b/netutils/asn.py @@ -24,3 +24,31 @@ def asn_to_int(asplain: str) -> int: asn = int(f"{int(asn_list[0]):016b}{int(asn_list[1]):016b}", 2) return asn return int(asplain) + + +def int_to_asn(asn_int: int) -> str: + """Convert integer to standardized asplain notation for BGP ASN. + + Args: + asn_int: Integer value of the BGP ASN. + + Returns: + `asplain` notated BGP ASN as a string. + + Examples: + >>> from netutils.asn import int_to_asn + >>> int_to_asn(65000) + '65000' + >>> int_to_asn(4259840111) + '65000.111' + >>> + """ + if isinstance(asn_int, str): + asn_int = int(asn_int) + if asn_int > 2**32 - 1 or asn_int < 1: + raise ValueError(f"`{str(asn_int)}` is not within range of 1 - 2^32-1") + if asn_int >= 2**16: + asn1 = asn_int >> 16 + asn2 = asn_int & ((1 << 16) - 1) + return f"{asn1}.{asn2}" + return str(asn_int) diff --git a/netutils/utils.py b/netutils/utils.py index 9580c278..9eefd912 100644 --- a/netutils/utils.py +++ b/netutils/utils.py @@ -5,6 +5,7 @@ _JINJA2_FUNCTION_MAPPINGS = { "asn_to_int": "asn.asn_to_int", + "int_to_asn": "asn.int_to_asn", "name_to_bits": "bandwidth.name_to_bits", "name_to_bytes": "bandwidth.name_to_bytes", "bits_to_name": "bandwidth.bits_to_name", diff --git a/tests/unit/test_asn.py b/tests/unit/test_asn.py index 045110c2..b73a8aa8 100644 --- a/tests/unit/test_asn.py +++ b/tests/unit/test_asn.py @@ -12,7 +12,39 @@ {"sent": {"asplain": "0.65526"}, "received": 65526}, ] +int_to_asn = [ + {"sent": {"asn_int": 6500}, "received": "6500"}, + {"sent": {"asn_int": 425985111}, "received": "6500.1111"}, + {"sent": {"asn_int": 425984001}, "received": "6500.1"}, + {"sent": {"asn_int": 65546}, "received": "1.10"}, + {"sent": {"asn_int": 65526}, "received": "65526"}, + {"sent": {"asn_int": "65526"}, "received": "65526"}, + {"sent": {"asn_int": "65535"}, "received": "65535"}, + {"sent": {"asn_int": "65536"}, "received": "1.0"}, + {"sent": {"asn_int": "425985111"}, "received": "6500.1111"}, + {"sent": {"asn_int": 4294967295}, "received": "65535.65535"}, +] + +int_to_asn_exceptions = [ + {"asn_int": "one22"}, + {"asn_int": "not_an_int"}, + {"asn_int": "4294967296"}, + {"asn_int": "0"}, + {"asn_int": -1}, +] + @pytest.mark.parametrize("data", asn_to_int) def test_asn_to_int(data): assert asn.asn_to_int(**data["sent"]) == data["received"] + + +@pytest.mark.parametrize("data", int_to_asn) +def test_int_to_asn(data): + assert asn.int_to_asn(**data["sent"]) == data["received"] + + +@pytest.mark.parametrize("data", int_to_asn_exceptions) +def test_int_to_asn_exceptions(data): + with pytest.raises(ValueError): + assert asn.int_to_asn(**data) From 9e539575abfa3ee4300071f168438940c0881de6 Mon Sep 17 00:00:00 2001 From: Ken Celenza Date: Fri, 4 Aug 2023 19:59:42 -0400 Subject: [PATCH 04/21] Update pin for mkdocs classy (#333) --- docs/requirements.txt | 2 +- poetry.lock | 8 ++++---- pyproject.toml | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index a8f5d07b..33f654ef 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,7 +1,7 @@ mkdocs==1.4.3 # Material for MkDocs theme mkdocs-material==8.5.8 -mkdocs-python-classy==0.1.2 +mkdocs-python-classy==0.1.3 # Render custom markdown for version added/changed/remove notes mkdocs-version-annotations==1.0.0 # Automatic documentation from sources, for MkDocs diff --git a/poetry.lock b/poetry.lock index 8b3f4db8..6d441dfc 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1082,13 +1082,13 @@ files = [ [[package]] name = "mkdocs-python-classy" -version = "0.1.2" +version = "0.1.3" description = "Mkdocs plugin to view Python Classes." optional = false python-versions = ">=3.8,<4.0" files = [ - {file = "mkdocs_python_classy-0.1.2-py3-none-any.whl", hash = "sha256:73843815a6aec1ce2088177a5920b5df7f3d208291e21b82dbd1c40271adb42f"}, - {file = "mkdocs_python_classy-0.1.2.tar.gz", hash = "sha256:89e7053e2a30351a068108e6a337479d77db8c1f3e74f3e3507a1322ccb84683"}, + {file = "mkdocs_python_classy-0.1.3-py3-none-any.whl", hash = "sha256:fdcfe1cffb39069abdd00873556a88493f463448fad27337ca781e39ad3472a8"}, + {file = "mkdocs_python_classy-0.1.3.tar.gz", hash = "sha256:a6ffdc7ac995b5773eaca97aea88cab43c10bb8e127b238ec052bd92389cc175"}, ] [package.dependencies] @@ -2259,4 +2259,4 @@ optionals = ["napalm"] [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "be93681a86ecec82804fec9ab1dcd4f6c8c81cb6b455cd6694ac4d4377239232" +content-hash = "aadbc2775614f7bea9461525b0490a12c6358995cca11489aed3a38c8b31ef9f" diff --git a/pyproject.toml b/pyproject.toml index fb36b988..fda0f3db 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,7 +50,7 @@ mkdocs-material = "8.5.8" mkdocstrings = "0.22.0" mkdocstrings-python = "1.1.2" mkdocs-version-annotations = "1.0.0" -mkdocs-python-classy = "0.1.2" +mkdocs-python-classy = "0.1.3" [tool.black] line-length = 120 From 137ffbe42d01c3fe7a214c52296600b088b100f7 Mon Sep 17 00:00:00 2001 From: Ken Celenza Date: Fri, 4 Aug 2023 21:50:01 -0400 Subject: [PATCH 05/21] Doc test (#334) * Test installation method * fix yamllint --- .readthedocs.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.readthedocs.yml b/.readthedocs.yml index a9d358ef..b60d928e 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -20,3 +20,5 @@ mkdocs: python: install: - requirements: "docs/requirements.txt" + - method: "pip" + path: "." From 5be374d2da04a0e71b2cb9a5f7617ed733237372 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 10 Aug 2023 21:53:17 -0600 Subject: [PATCH 06/21] Flat: latest data (2023-08-10T08:03:23.368Z) (#337) { "date": "2023-08-10T08:03:23.368Z", "files": [ { "name": "netutils/data_files/oui_mappings.py", "deltaBytes": 4450, "source": "https://standards-oui.ieee.org" } ] } Co-authored-by: flat-data --- netutils/data_files/oui_mappings.py | 125 ++++++++++++++++++++++++++-- 1 file changed, 119 insertions(+), 6 deletions(-) diff --git a/netutils/data_files/oui_mappings.py b/netutils/data_files/oui_mappings.py index 3b24a80c..be42a658 100644 --- a/netutils/data_files/oui_mappings.py +++ b/netutils/data_files/oui_mappings.py @@ -414,7 +414,7 @@ "000199": "HeiSei Electronics", "00019a": "LEUNIG GmbH", "00019b": "Kyoto Microcomputer Co., Ltd.", - "00019c": "JDS Uniphase Inc.", + "00019c": "Lumentum", "00019d": "E-Control Systems, Inc.", "00019e": "ESS Technology, Inc.", "00019f": "ReadyNet", @@ -806,7 +806,7 @@ "000321": "Reco Research Co., Ltd.", "000322": "IDIS Co., Ltd.", "000323": "Cornet Technology, Inc.", - "000324": "SANYO Techno Solutions Tottori Co., Ltd.", + "000324": "LIMNO Co., Ltd.", "000325": "Arima Computer Corp.", "000326": "Iwasaki Information Systems Co., Ltd.", "000327": "HMS Industrial Networks", @@ -1589,7 +1589,7 @@ "000630": "Adtranz Sweden", "000631": "Calix Inc.", "000632": "Mesco Engineering GmbH", - "000633": "Cross Match Technologies GmbH", + "000633": "Crossmatch Technologies/HID Global", "000634": "GTE Airfone Inc.", "000635": "PacketAir Networks, Inc.", "000636": "Jedai Broadband Networks", @@ -10100,7 +10100,7 @@ "00308b": "Brix Networks", "00308c": "Quantum Corporation", "00308d": "Pinnacle Systems, Inc.", - "00308e": "CROSS MATCH TECHNOLOGIES, INC.", + "00308e": "Crossmatch Technologies/HID Global", "00308f": "MICRILOR, Inc.", "003090": "CYRA TECHNOLOGIES, INC.", "003091": "TAIWAN FIRST LINE ELEC. CORP.", @@ -12573,6 +12573,7 @@ "00d318": "SPG Controls", "00d38d": "Hotel Technology Next Generation", "00d49e": "Intel Corporate", + "00d598": "BOPEL MOBILE TECHNOLOGY CO.,LIMITED", "00d632": "GE Energy", "00d6fe": "Cisco Systems, Inc", "00d76d": "Intel Corporate", @@ -12976,6 +12977,7 @@ "042234": "Wireless Standard Extensions", "0425c5": "HUAWEI TECHNOLOGIES CO.,LTD", "0425e0": "Taicang T&W Electronics", + "0425e8": "Texas Instruments", "0425f0": "Nokia", "042605": "Bosch Building Automation GmbH", "042665": "Apple, Inc.", @@ -13475,6 +13477,7 @@ "082697": "Zyxel Communications Corporation", "0826ae": "IEEE Registration Authority", "082719": "APS systems/electronic AG", + "08276b": "Huawei Device Co., Ltd.", "0827ce": "NAGANO KEIKI CO., LTD.", "082802": "SHENZHEN CHUANGWEI-RGB ELECTRONICS CO.,LTD", "082ad0": "SRD Innovations Inc.", @@ -14411,6 +14414,7 @@ "14019c": "Ubyon Inc.", "1402ec": "Hewlett Packard Enterprise", "140467": "SNK Technologies Co.,Ltd.", + "14064c": "Vogl Electronic GmbH", "140708": "CP PLUS GMBH & CO. KG", "1407e0": "Abrantix AG", "1409b4": "zte corporation", @@ -14683,6 +14687,7 @@ "14d64d": "D-Link International", "14d76e": "CONCH ELECTRONIC Co.,Ltd", "14d864": "TP-LINK TECHNOLOGIES CO.,LTD.", + "14dab9": "Huawei Device Co., Ltd.", "14dae9": "ASUSTek COMPUTER INC.", "14db85": "S NET MEDIA", "14dc51": "Xiamen Cheerzing IOT Technology Co.,Ltd.", @@ -14804,6 +14809,7 @@ "183da2": "Intel Corporate", "183eef": "Apple, Inc.", "183f47": "Samsung Electronics Co.,Ltd", + "183f70": "Apple, Inc.", "1840a4": "Shenzhen Trylong Smart Science and Technology Co., Ltd.", "1841fe": "Digital 14", "18421d": "Private", @@ -14857,6 +14863,7 @@ "185ae8": "Zenotech.Co.,Ltd", "185b00": "Nokia", "185bb3": "Samsung Electronics Co.,Ltd", + "185d6f": "N3com", "185d9a": "BobjGear LLC", "185e0b": "zte corporation", "185e0f": "Intel Corporate", @@ -15403,6 +15410,7 @@ "2002af": "Murata Manufacturing Co., Ltd.", "2002fe": "Hangzhou Dangbei Network Technology Co., Ltd", "20040f": "Dell Inc.", + "200484": "Apple, Inc.", "2004f3": "Honor Device Co., Ltd.", "200505": "RADMAX COMMUNICATION PRIVATE LIMITED", "2005e8": "OOO InProMedia", @@ -15545,6 +15553,7 @@ "206a94": "Hitron Technologies. Inc", "206aff": "Atlas Elektronik UK Limited", "206be7": "TP-LINK TECHNOLOGIES CO.,LTD.", + "206bf4": "Huawei Device Co., Ltd.", "206c8a": "Extreme Networks Headquarters", "206d31": "FIREWALLA INC", "206e9c": "Samsung Electronics Co.,Ltd", @@ -16113,6 +16122,7 @@ "283a4d": "Limited", "283b82": "D-Link International", "283b96": "Cool Control LTD", + "283c90": "Texas Instruments", "283ce4": "HUAWEI TECHNOLOGIES CO.,LTD", "283dc2": "Samsung Electronics Co.,Ltd", "283e0c": "Preferred Robotics, Inc.", @@ -16122,7 +16132,7 @@ "284121": "OptiSense Network, LLC", "2841c6": "HUAWEI TECHNOLOGIES CO.,LTD", "2841ec": "HUAWEI TECHNOLOGIES CO.,LTD", - "284430": "Ltd", + "284430": "Arcade Communications Ltd.", "2847aa": "Nokia Corporation", "284846": "GridCentric Inc.", "2848e7": "Huawei Device Co., Ltd.", @@ -16190,6 +16200,7 @@ "2880a2": "Novatel Wireless Solutions, Inc.", "28827c": "Bosch Automative products(Suzhou)Co.,Ltd Changzhou Branch", "288335": "Samsung Electronics Co.,Ltd", + "2883c9": "Apple, Inc.", "28840e": "silicon valley immigration service ", "2884fa": "SHARP Corporation", "28852d": "Touch Networks", @@ -16520,6 +16531,7 @@ "2c67ab": "EZELINK TELECOM", "2c67fb": "ShenZhen Zhengjili Electronics Co., LTD", "2c691d": "IEEE Registration Authority", + "2c693e": "HUAWEI TECHNOLOGIES CO.,LTD", "2c69ba": "RF Controls, LLC", "2c69cc": "Valeo Detection Systems ", "2c6a6f": "IEEE Registration Authority", @@ -16675,6 +16687,7 @@ "2ce871": "Alert Metalguard ApS", "2cea7f": "Dell Inc.", "2ceadc": "ASKEY COMPUTER CORP", + "2cedb0": "HUAWEI TECHNOLOGIES CO.,LTD", "2cedeb": "Alpheus Digital Company Limited", "2cee26": "Petroleum Geo-Services", "2cf05d": "Micro-Star INTL CO., LTD.", @@ -16952,6 +16965,7 @@ "30d659": "Merging Technologies SA", "30d6c9": "Samsung Electronics Co.,Ltd", "30d7a1": "Apple, Inc.", + "30d875": "Apple, Inc.", "30d941": "Raydium Semiconductor Corp.", "30d9d9": "Apple, Inc.", "30de4b": "TP-Link Corporation Limited", @@ -17117,6 +17131,7 @@ "345d9e": "Sagemcom Broadband SAS", "345da8": "Cisco Systems, Inc", "345e08": "Roku, Inc", + "345ee7": "Hangzhou ChengFengErLai Digial Technology Co.,Ltd.", "3460f9": "TP-Link Corporation Limited", "346178": "The Boeing Company", "346288": "Cisco Systems, Inc", @@ -17231,6 +17246,7 @@ "34af2c": "Nintendo Co., Ltd.", "34afa3": "Recogni Inc", "34afb3": "Amazon Technologies Inc.", + "34b1eb": "Apple, Inc.", "34b1f7": "Texas Instruments", "34b20a": "Huawei Device Co., Ltd.", "34b354": "HUAWEI TECHNOLOGIES CO.,LTD", @@ -17712,6 +17728,7 @@ "3c10e6": "PHAZR Inc.", "3c11b2": "Fraunhofer FIT", "3c135a": "Xiaomi Communications Co Ltd", + "3c13bb": "HUAWEI TECHNOLOGIES CO.,LTD", "3c13cc": "Cisco Systems, Inc", "3c1512": "Shenzhen Huanhu Technology Co.,Ltd", "3c15c2": "Apple, Inc.", @@ -17739,6 +17756,7 @@ "3c22fb": "Apple, Inc.", "3c24f0": "IEEE Registration Authority", "3c25d7": "Nokia Corporation", + "3c25f8": "Dell Inc.", "3c26d5": "Sotera Wireless", "3c26e4": "Cisco Systems, Inc", "3c2763": "SLE quality engineering GmbH & Co. KG", @@ -17797,6 +17815,7 @@ "3c53d7": "CEDES AG", "3c5447": "HUAWEI TECHNOLOGIES CO.,LTD", "3c5576": "CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD.", + "3c55db": "Shenzhen Skyworth Digital Technology CO., Ltd", "3c5731": "Cisco Systems, Inc", "3c574f": "China Mobile Group Device Co.,Ltd.", "3c576c": "Samsung Electronics Co.,Ltd", @@ -17955,6 +17974,7 @@ "3ccd57": "Beijing Xiaomi Mobile Software Co., Ltd", "3ccd5a": "Technische Alternative GmbH", "3ccd5d": "HUAWEI TECHNOLOGIES CO.,LTD", + "3ccd73": "Nebula Electronic Technology Corporation", "3ccd93": "LG ELECTRONICS INC", "3cce0d": "Shenzhen juduoping Technology Co.,Ltd", "3cce15": "Mercedes-Benz USA, LLC", @@ -18082,6 +18102,7 @@ "40331a": "Apple, Inc.", "40336c": "Godrej & Boyce Mfg. co. ltd", "4035e6": "Samsung Electronics Co.,Ltd", + "403668": "E&B TELECOM", "4037ad": "Macro Image Technology, Inc.", "403b7b": "Huawei Device Co., Ltd.", "403cfc": "Apple, Inc.", @@ -18259,6 +18280,7 @@ "40bd9e": "Physio-Control, Inc", "40beee": "Shenzhen Yunding Information Technology Co.,Ltd", "40bf17": "Digistar Telecom. SA", + "40c0ee": "365mesh Pty Ltd", "40c1f6": "Shenzhen Jingxun Technology Co., Ltd.", "40c245": "Shenzhen Hexicom Technology Co., Ltd.", "40c2ba": "CO., LTD. ", @@ -18541,6 +18563,7 @@ "449ef9": "vivo Mobile Communication Co., Ltd.", "449f46": "Huawei Device Co., Ltd.", "449f7f": "DataCore Software Corporation", + "44a038": "Huawei Device Co., Ltd.", "44a191": "HUAWEI TECHNOLOGIES CO.,LTD", "44a3c7": "zte corporation", "44a42d": "TCT mobile ltd", @@ -18587,6 +18610,7 @@ "44c306": "SIFROM Inc.", "44c346": "HUAWEI TECHNOLOGIES CO.,LTD", "44c39b": "OOO RUBEZH NPO", + "44c3b6": "HUAWEI TECHNOLOGIES CO.,LTD", "44c4a9": "Opticom Communication, LLC", "44c56f": "Electronic Co., Ltd", "44c65d": "Apple, Inc.", @@ -18608,6 +18632,7 @@ "44d437": "Inteno Broadband Technology AB", "44d453": "Sagemcom Broadband SAS", "44d454": "Sagemcom Broadband SAS", + "44d47f": "INGENICO TERMINALS SAS", "44d4e0": "Sony Corporation", "44d506": "Sichuan Tianyi Comheart Telecom Co.,LTD", "44d5a5": "AddOn Computer", @@ -18800,6 +18825,7 @@ "485ded": "Sichuan Tianyi Comheart Telecom Co.,LTD", "485f08": "TP-LINK TECHNOLOGIES CO.,LTD.", "485f99": "Limited", + "485fdf": "zte corporation", "4860bc": "Apple, Inc.", "4861a3": "Concern Axion JSC", "4861ee": "Samsung Electronics Co.,Ltd", @@ -18842,6 +18868,7 @@ "4881d4": "Ruijie Networks Co.,LTD", "488244": "Life Fitness / Div. of Brunswick", "4882f2": "Appel Elektronik GmbH", + "48836f": "EM Microelectronic", "4883b4": "GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD", "4883c7": "Sagemcom Broadband SAS", "4886e8": "Microsoft Corporation", @@ -18938,6 +18965,7 @@ "48cac6": "UNION MAN TECHNOLOGY CO.,LTD", "48cb6e": "Ltd", "48cdd3": "HUAWEI TECHNOLOGIES CO.,LTD", + "48d017": "Telecom Infra Project", "48d0cf": "Universal Electronics, Inc.", "48d18e": "Metis Communication Co.,Ltd", "48d224": "Liteon Technology Corporation", @@ -18980,6 +19008,7 @@ "48eb62": "Murata Manufacturing Co., Ltd.", "48ec5b": "Nokia Solutions and Networks GmbH & Co. KG", "48ed80": "daesung eltec", + "48ede6": "Zyxel Communications Corporation", "48ee07": "Silver Palm Technologies LLC", "48ee0c": "D-Link International", "48ee86": "Co.,Ltd", @@ -19301,6 +19330,7 @@ "4ce705": "Siemens Industrial Automation Products Ltd., Chengdu", "4ce933": "RailComm, LLC", "4ce9e4": "New H3C Technologies Co., Ltd", + "4cea41": "IEEE Registration Authority", "4ceaae": "GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD", "4ceb42": "Intel Corporate", "4ceb76": "Murrelektronik GmbH", @@ -19565,10 +19595,12 @@ "50a0a4": "Nokia", "50a0bf": "Alba Fiber Systems Inc.", "50a132": "Shenzhen MiaoMing Intelligent Technology Co.,Ltd", + "50a1f3": "Huawei Device Co., Ltd.", "50a4c8": "Samsung Electronics Co.,Ltd", "50a4d0": "IEEE Registration Authority", "50a5dc": "ARRIS Group, Inc.", "50a67f": "Apple, Inc.", + "50a6d8": "Apple, Inc.", "50a6e3": "David Clark Company", "50a715": "Aboundi, Inc.", "50a72b": "HUAWEI TECHNOLOGIES CO.,LTD", @@ -19947,6 +19979,7 @@ "54dba2": "Fibrain", "54dc1d": "Co.,Ltd", "54ded0": "Sevio Srl", + "54ded3": "zte corporation", "54df00": "Ulterius Technologies, LLC", "54df1b": "Vestel Elektronik San ve Tic. A.S.", "54df24": "Fiberhome Telecommunication Technologies Co.,LTD", @@ -20207,6 +20240,7 @@ "58b961": "SOLEM Electronique", "58b965": "Apple, Inc.", "58b9e1": "Crystalfontz America, Inc.", + "58bad3": "NANJING CASELA TECHNOLOGIES CORPORATION LIMITED", "58bad4": "HUAWEI TECHNOLOGIES CO.,LTD", "58bc27": "Cisco Systems, Inc", "58bc8f": "Cognitive Systems Corp.", @@ -20245,6 +20279,7 @@ "58d697": "GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD", "58d6d3": "Dairy Cheq Inc", "58d759": "HUAWEI TECHNOLOGIES CO.,LTD", + "58d8a7": "Bird Home Automation GmbH", "58d9c3": "Motorola Mobility LLC, a Lenovo Company", "58d9d5": "Tenda Technology Co.,Ltd.Dongguan branch", "58db15": "TECNO MOBILE LIMITED", @@ -20386,6 +20421,7 @@ "5c521e": "Nintendo Co.,Ltd", "5c5230": "Apple, Inc.", "5c5284": "Apple, Inc.", + "5c53b4": "EM Microelectronic", "5c53c3": "Ubee Interactive Co., Limited", "5c546d": "HUAWEI TECHNOLOGIES CO.,LTD", "5c5578": "iryx corp", @@ -20616,6 +20652,7 @@ "5cfb7c": "Shenzhen Jingxun Software Telecommunication Technology Co.,Ltd", "5cfc66": "Cisco Systems, Inc", "5cfc6e": "Sichuan Tianyi Comheart Telecom Co.,LTD", + "5cfce1": "Resideo", "5cfe9e": "Wiwynn Corporation Tainan Branch", "5cff35": "Wistron Corporation", "5cffff": "Shenzhen Kezhonglong Optoelectronic Technology Co., Ltd", @@ -20671,6 +20708,7 @@ "602232": "Ubiquiti Inc", "6023a4": "Sichuan AI-Link Technology Co., Ltd.", "6024c1": "Jiangsu Zhongxun Electronic Technology Co., Ltd", + "602602": "Texas Instruments", "6026aa": "Cisco Systems, Inc", "6026ef": "Aruba, a Hewlett Packard Enterprise Company", "60271c": "VIDEOR E. Hartig GmbH", @@ -20715,6 +20753,7 @@ "6047d4": "FORICS Electronic Technology Co., Ltd.", "604826": "Newbridge Technologies Int. Ltd.", "60489c": "YIPPEE ELECTRONICS CO.,LIMITED", + "604966": "Shenzhen Dingsheng Technology Co., Ltd.", "6049c1": "Avaya Inc", "604a1c": "SUYIN Corporation", "604baa": "Magic Leap, Inc.", @@ -20733,13 +20772,14 @@ "605718": "Intel Corporate", "605747": "CIG SHANGHAI CO LTD", "60577d": "eero inc.", + "6057c8": "Apple, Inc.", "605801": "Shandong ZTop Microelectronics Co., Ltd.", "605b30": "Dell Inc.", "605bb4": "AzureWave Technology Inc.", "605e4f": "Huawei Device Co., Ltd.", "605f8d": "eero inc.", "60601f": "SZ DJI TECHNOLOGY CO.,LTD", - "606134": "Genesis Technical Systems Corp", + "606134": "Arcade Communications Ltd.", "6061df": "Z-meta Research LLC", "60634c": "D-Link International", "6063f9": "Ciholas, Inc.", @@ -21044,6 +21084,7 @@ "643e0a": "HUAWEI TECHNOLOGIES CO.,LTD", "643e8c": "HUAWEI TECHNOLOGIES CO.,LTD", "643f5f": "Exablaze", + "6441e6": "Apple, Inc.", "644212": "Shenzhen Water World Information Co.,Ltd.", "644214": "Swisscom Energy Solutions AG", "644346": "GuangDong Quick Network Computer CO.,LTD", @@ -21366,6 +21407,7 @@ "683e02": "SIEMENS AG, Digital Factory, Motion Control System", "683e26": "Intel Corporate", "683e34": "MEIZU Technology Co., Ltd.", + "683ec0": "Apple, Inc.", "683eec": "ERECA", "683f1e": "EFFECT Photonics B.V.", "683f7d": "INGRAM MICRO SERVICES", @@ -21389,6 +21431,7 @@ "684f64": "Dell Inc.", "68505d": "Halo Technologies", "6851b7": "PowerCloud Systems, Inc.", + "685210": "MCS Logic", "6852d6": "UGame Technology Co.,Ltd", "68536c": "SPnS Co.,Ltd", "685388": "P&S Technology", @@ -21537,6 +21580,7 @@ "68c8eb": "Rockwell Automation", "68c90b": "Texas Instruments", "68ca00": "Octopus Systems Limited", + "68cac4": "Apple, Inc.", "68cae4": "Cisco Systems, Inc", "68cc6e": "HUAWEI TECHNOLOGIES CO.,LTD", "68cc9c": "Mine Site Technologies", @@ -21637,6 +21681,7 @@ "6c146e": "HUAWEI TECHNOLOGIES CO.,LTD", "6c14f7": "Erhardt+Leimer GmbH", "6c1524": "IEEE Registration Authority", + "6c1544": "Microsoft Corporation", "6c15f9": "Nautronix Limited", "6c160e": "ShotTracker", "6c1632": "HUAWEI TECHNOLOGIES CO.,LTD", @@ -22218,8 +22263,10 @@ "70b950": "Texas Instruments", "70b9bb": "Shenzhen Hankvision Technology CO.,LTD", "70baef": "Hangzhou H3C Technologies Co., Limited", + "70bb5b": "Apple, Inc.", "70bbe9": "Xiaomi Communications Co Ltd", "70bc10": "Microsoft Corporation", + "70bdd2": "Adva Network Security GmbH", "70bf3e": "Charles River Laboratories", "70bf92": "GN Audio A/S", "70c6ac": "Bosch Automotive Aftermarket", @@ -22311,6 +22358,7 @@ "740abc": "LightwaveRF Technology Ltd", "740ae1": "Huawei Device Co., Ltd.", "740cee": "Huawei Device Co., Ltd.", + "740ea4": "Apple, Inc.", "740edb": "Optowiz Co., Ltd", "7411b2": "Cisco Systems, Inc", "7412b3": "CHONGQING FUGUI ELECTRONICS CO.,LTD.", @@ -22329,6 +22377,7 @@ "741e93": "Fiberhome Telecommunication Technologies Co.,LTD", "741f4a": "Hangzhou H3C Technologies Co., Limited", "741f79": "YOUNGKOOK ELECTRONICS CO.,LTD", + "74205f": "Shenzhen Zhongruixin Intelligent Technology Co., Ltd.", "7422bb": "Huawei Device Co., Ltd.", "742344": "Xiaomi Communications Co Ltd", "74249f": "TIBRO Corp.", @@ -22507,6 +22556,7 @@ "74978e": "Nova Labs", "749975": "IBM Corporation", "749ac0": "Cachengo, Inc.", + "749b89": "HUAWEI TECHNOLOGIES CO.,LTD", "749be8": "Hitron Technologies. Inc", "749c52": "Huizhou Desay SV Automotive Co., Ltd.", "749ce3": "KodaCloud Canada, Inc", @@ -22787,6 +22837,7 @@ "785dc8": "LG Electronics", "785ea2": "Sunitec Enterprise Co.,Ltd", "785ee8": "IEEE Registration Authority", + "785f28": "EM Microelectronic", "785f36": "Shenzhen Skyworth Digital Technology CO., Ltd", "785f4c": "Argox Information Co., Ltd.", "78605b": "TP-LINK TECHNOLOGIES CO.,LTD.", @@ -23113,6 +23164,7 @@ "7c477c": "IEEE Registration Authority", "7c48b2": "Vida Resources Lte Ltd", "7c49b9": "Plexus Manufacturing Sdn Bhd", + "7c49cf": "eero inc.", "7c49eb": "XIAOMI Electronics,CO.,LTD", "7c4a82": "Portsmith LLC", "7c4aa8": "MindTree Wireless PVT Ltd", @@ -23535,6 +23587,7 @@ "807b1e": "Corsair Memory, Inc.", "807b3e": "Samsung Electronics Co.,Ltd", "807b85": "IEEE Registration Authority", + "807c0a": "zte corporation", "807c62": "Hangzhou Hikvision Digital Technology Co.,Ltd.", "807d14": "HUAWEI TECHNOLOGIES CO.,LTD", "807d1b": "Neosystem Co. Ltd.", @@ -23683,6 +23736,7 @@ "80f3ef": "Meta Platforms Technologies, LLC", "80f503": "ARRIS Group, Inc.", "80f593": "IRCO Sistemas de Telecomunicación S.A.", + "80f5ae": "Hangzhou Hikvision Digital Technology Co.,Ltd.", "80f5b5": "Texas Instruments", "80f62e": "Hangzhou H3C Technologies Co., Limited", "80f7a6": "Shenzhen C-Data Technology Co., Ltd.", @@ -23937,6 +23991,7 @@ "84b8b8": "Mobility Technologies Communication Co., Ltd.", "84ba20": "Silicon Laboratories", "84ba3b": "CANON INC.", + "84bb26": "Texas Instruments", "84bb69": "ARRIS Group, Inc.", "84be52": "HUAWEI TECHNOLOGIES CO.,LTD", "84c0ef": "Samsung Electronics Co.,Ltd", @@ -24481,6 +24536,7 @@ "8c6078": "Swissbit AG", "8c60e7": "MPGIO CO.,LTD", "8c6102": "Beijing Baofengmojing Technologies Co., Ltd", + "8c6120": "EM Microelectronic", "8c61a3": "ARRIS Group, Inc.", "8c640b": "Beyond Devices d.o.o.", "8c6422": "Sony Corporation", @@ -24548,6 +24604,7 @@ "8c8caa": "Electronics Technology co., ltd", "8c8d28": "Intel Corporate", "8c8e0d": "zte corporation", + "8c8e4e": "Baylan Olcu Aletleri San. ve Tic.A.S.", "8c8e76": "taskit GmbH", "8c8ef2": "Apple, Inc.", "8c8f8b": "China Mobile Chongqing branch", @@ -24567,6 +24624,7 @@ "8c9806": "SHENZHEN SEI ROBOTICS CO.,LTD", "8c986b": "Apple, Inc.", "8c99e6": "TCT mobile ltd", + "8c9b2d": "PLANTRONICS, INC.", "8c9f3b": "Qingdao Hisense Communications Co.,Ltd.", "8ca048": "Beijing NeTopChip Technology Co.,LTD", "8ca2fd": "Starry, Inc.", @@ -24745,6 +24803,7 @@ "902aee": "Xiaomi Communications Co Ltd", "902b34": "GIGA-BYTE TECHNOLOGY CO.,LTD.", "902bd2": "HUAWEI TECHNOLOGIES CO.,LTD", + "902c09": "Apple, Inc.", "902cc7": "C-MAX Asia Limited", "902cfb": "CanTops Co,.Ltd.", "902d77": "Edgecore Americas Networking Corporation", @@ -24779,6 +24838,7 @@ "903fea": "HUAWEI TECHNOLOGIES CO.,LTD", "9043e2": "Cornami, Inc", "904506": "Tokyo Boeki Medisys Inc.", + "904528": "Nintendo Co.,Ltd", "9046a2": "Tedipay UK Ltd", "9046b7": "Vadaro Pte Ltd", "904716": "RORZE CORPORATION", @@ -24911,6 +24971,7 @@ "909c4a": "Apple, Inc.", "909d7d": "ARRIS Group, Inc.", "909de0": "Newland Design + Assoc. Inc.", + "909e24": "ekey biometric systems gmbh", "909f33": "EFM Networks", "909f43": "Accutron Instruments Inc.", "90a137": "Beijing Splendidtel Communication Technology Co,. Ltd", @@ -24921,6 +24982,7 @@ "90a365": "HMD Global Oy", "90a46a": "SISNET CO., LTD", "90a4de": "Wistron Neweb Corporation", + "90a57d": "Huawei Device Co., Ltd.", "90a5af": "HUAWEI TECHNOLOGIES CO.,LTD", "90a62f": "NAVER", "90a6bf": "Quectel Wireless Solutions Co.,Ltd.", @@ -25068,6 +25130,7 @@ "9411da": "ITF Fröschl GmbH", "941457": "Shenzhen Sundray Technologies Company Limited", "94147a": "vivo Mobile Communication Co., Ltd.", + "9415b2": "Huawei Device Co., Ltd.", "941625": "Apple, Inc.", "941673": "Point Core SARL", "941700": "Xiaomi Communications Co Ltd", @@ -25113,6 +25176,7 @@ "943cc6": "Espressif Inc.", "943dc9": "Asahi Net, Inc.", "943ee4": "WiSA Technologies Inc", + "943f0c": "Genexis B.V.", "943fbb": "JSC RPC Istok named after Shokin", "943fc2": "Hewlett Packard Enterprise", "943fd6": "Apple, Inc.", @@ -25912,6 +25976,7 @@ "9caf6f": "ITEL MOBILE LIMITED", "9cafca": "Cisco Systems, Inc", "9cb008": "Ubiquitous Computing Technology Corporation", + "9cb1dc": "Earda Technologies co Ltd", "9cb206": "HMS Industrial Networks", "9cb2b2": "HUAWEI TECHNOLOGIES CO.,LTD", "9cb2e8": "HUAWEI TECHNOLOGIES CO.,LTD", @@ -25991,6 +26056,7 @@ "9cf387": "Apple, Inc.", "9cf48e": "Apple, Inc.", "9cf531": "GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD", + "9cf55f": "Harman/Becker Automotive Systems GmbH", "9cf61a": "Carrier Fire & Security", "9cf67d": "Ricardo Prague, s.r.o.", "9cf6dd": "IEEE Registration Authority", @@ -26118,6 +26184,7 @@ "a04fd4": "ADB Broadband Italia", "a0510b": "Intel Corporate", "a051c6": "Avaya Inc", + "a05272": "Apple, Inc.", "a05394": "Shenzhen zediel co., Ltd.", "a0554f": "Cisco Systems, Inc", "a055de": "ARRIS Group, Inc.", @@ -26244,6 +26311,7 @@ "a0b100": "ShenZhen Cando Electronics Co.,Ltd", "a0b339": "Intel Corporate", "a0b3cc": "Hewlett Packard", + "a0b40f": "Apple, Inc.", "a0b437": "GD Mission Systems", "a0b439": "Cisco Systems, Inc", "a0b4a5": "Samsung Electronics Co.,Ltd", @@ -26339,6 +26407,7 @@ "a0ff0c": "Hangzhou Hikvision Digital Technology Co.,Ltd.", "a0ff22": "SHENZHEN APICAL TECHNOLOGY CO., LTD", "a0ff70": "Vantiva USA LLC", + "a4004e": "Cisco Systems, Inc", "a400e2": "HUAWEI TECHNOLOGIES CO.,LTD", "a40130": "ABIsystems Co., LTD", "a402b9": "Intel Corporate", @@ -26359,6 +26428,7 @@ "a40e2b": "Facebook Inc", "a40e75": "Aruba, a Hewlett Packard Enterprise Company", "a40f98": "GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD", + "a410b6": "Cisco Systems, Inc", "a41115": "Robert Bosch Engineering and Business Solutions pvt. Ltd.", "a41162": "Arlo Technology", "a41163": "IEEE Registration Authority", @@ -26536,6 +26606,7 @@ "a494dc": "Infinite Clouds", "a49733": "ASKEY COMPUTER CORP", "a4975c": "VTech Telecommunications Ltd.", + "a4978a": "LEAR", "a497b1": "CHONGQING FUGUI ELECTRONICS CO.,LTD.", "a497bb": "Hitachi Industrial Equipment Systems Co.,Ltd", "a49813": "ARRIS Group, Inc.", @@ -26677,7 +26748,9 @@ "a4f465": "ITEL MOBILE LIMITED", "a4f4c2": "VNPT TECHNOLOGY", "a4f522": "CHOFU SEISAKUSHO CO.,LTD", + "a4f6e8": "Apple, Inc.", "a4f7d0": "LAN Accessories Co., Ltd.", + "a4f841": "Apple, Inc.", "a4f933": "Intel Corporate", "a4f9e4": "AirVine Scientific, Inc.", "a4fa76": "New H3C Technologies Co., Ltd", @@ -26834,6 +26907,7 @@ "a8798d": "Samsung Electronics Co.,Ltd", "a87b39": "Nokia Corporation", "a87c01": "Samsung Electronics Co.,Ltd", + "a87c45": "HUAWEI TECHNOLOGIES CO.,LTD", "a87cf8": "Apple, Inc.", "a87d12": "HUAWEI TECHNOLOGIES CO.,LTD", "a87e33": "Nokia Danmark A/S", @@ -27304,6 +27378,7 @@ "acdce5": "Procter & Gamble Company", "acde48": "Private", "acdf9f": "Arcadyan Corporation", + "acdfa1": "Apple, Inc.", "ace010": "Liteon Technology Corporation", "ace069": "ISAAC Instruments", "ace0d6": "koreabts", @@ -27599,6 +27674,7 @@ "b0c952": "GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD", "b0c95b": "Beijing Symtech CO.,LTD", "b0ca68": "Apple, Inc.", + "b0cae7": "Huawei Device Co., Ltd.", "b0ccfe": "Huawei Device Co., Ltd.", "b0ce18": "Zhejiang shenghui lighting co.,Ltd", "b0cf0e": "Mellanox Technologies, Inc.", @@ -28751,6 +28827,7 @@ "c05d39": "Jiangsu Huitong Group Co.,Ltd.", "c05e6f": "V. Stonkaus firma Kodinis Raktas", "c05e79": "SHENZHEN HUAXUN ARK TECHNOLOGIES CO.,LTD", + "c05f87": "Legrand INTELLIGENT ELECTRICAL(HUIZHOU)CO.,LTD.", "c06118": "TP-LINK TECHNOLOGIES CO.,LTD.", "c0613d": "BioIntelliSense, Inc.", "c0619a": "IEEE Registration Authority", @@ -28896,6 +28973,7 @@ "c0d391": "IEEE Registration Authority", "c0d3c0": "Samsung Electronics Co.,Ltd", "c0d46b": "Huawei Device Co., Ltd.", + "c0d60a": "Texas Instruments", "c0d682": "Arista Networks", "c0d7aa": "Arcadyan Corporation", "c0d834": "xvtec ltd", @@ -29254,6 +29332,7 @@ "c4e510": "Mechatro, Inc.", "c4e532": "Arcadyan Corporation", "c4e5b1": "Suzhou PanKore Integrated Circuit Technology Co. Ltd.", + "c4e733": "Clear Align LLC", "c4e7be": "SCSpro Co.,Ltd", "c4e90a": "D-Link International", "c4e92f": "AB Sciex", @@ -29306,6 +29385,7 @@ "c809a8": "Intel Corporate", "c80a35": "Qingdao Hisense Smart Life Technology Co., Ltd", "c80aa9": "Quanta Computer Inc.", + "c80c53": "China Mobile Group Device Co.,Ltd.", "c80cc8": "HUAWEI TECHNOLOGIES CO.,LTD", "c80d32": "Holoplot GmbH", "c80e14": "AVM Audiovisuelles Marketing und Computersysteme GmbH", @@ -29381,6 +29461,7 @@ "c84529": "IMK Networks Co.,Ltd", "c84544": "Co, Ltd", "c8458f": "Wyler AG", + "c84709": "Cisco Systems, Inc", "c84782": "Areson Technology Corp.", "c8478c": "Beken Corporation", "c848f5": "MEDISON Xray Co., Ltd", @@ -29483,6 +29564,7 @@ "c89bd7": "Realme Chongqing Mobile Telecommunications Corp.,Ltd.", "c89c13": "Inspiremobile", "c89c1d": "Cisco Systems, Inc", + "c89cbb": "Taicang T&W Electronics", "c89cdc": "Elitegroup Computer Systems Co.,Ltd.", "c89d18": "Huawei Device Co., Ltd.", "c89d6d": "ITEL MOBILE LIMITED", @@ -29550,6 +29632,7 @@ "c8ca79": "Ciena Corporation", "c8cb9e": "Intel Corporate", "c8cbb8": "Hewlett Packard", + "c8ccb5": "Hunter Douglas", "c8cd72": "Sagemcom Broadband SAS", "c8d019": "Shanghai Tigercel Communication Technology Co.,Ltd", "c8d083": "Apple, Inc.", @@ -29689,6 +29772,7 @@ "cc34d7": "GEWISS S.P.A.", "cc3540": "Vantiva USA LLC", "cc355a": "SecuGen Corporation", + "cc36cf": "Cisco Systems, Inc", "cc37ab": "Edgecore Networks Corporation", "cc398c": "Shiningtek", "cc3a61": "SAMSUNG ELECTRO MECHANICS CO., LTD.", @@ -29710,6 +29794,7 @@ "cc4210": "Xiaomi Communications Co Ltd", "cc43e3": "Trump s.a.", "cc4463": "Apple, Inc.", + "cc45a5": "Texas Instruments", "cc4639": "WAAV, Inc.", "cc464e": "Samsung Electronics Co.,Ltd", "cc46d6": "Cisco Systems, Inc", @@ -30154,6 +30239,7 @@ "d09d0a": "LINKCOM", "d09dab": "TCT mobile ltd", "d09fd9": "IEEE Registration Authority", + "d0a0bb": "Shenzhen iComm Semiconductor CO.,LTD", "d0a0d6": "ChengDu TD Tech", "d0a311": "Neuberger Gebäudeautomation GmbH", "d0a46f": "China Dragon Technology Limited", @@ -30217,6 +30303,7 @@ "d0d471": "MVTECH co., Ltd", "d0d6cc": "Wintop", "d0d783": "HUAWEI TECHNOLOGIES CO.,LTD", + "d0d7be": "HUAWEI TECHNOLOGIES CO.,LTD", "d0d94f": "IEEE Registration Authority", "d0dad7": "Apple, Inc.", "d0db32": "Nokia Corporation", @@ -30341,6 +30428,7 @@ "d43fcb": "ARRIS Group, Inc.", "d440d0": "OCOSMOS Co., LTD", "d440f0": "HUAWEI TECHNOLOGIES CO.,LTD", + "d4413f": "Gen IV Technology LLC", "d44165": "Sichuan Tianyi Comheart Telecom Co.,LTD", "d4430e": "Zhejiang Dahua Technology Co., Ltd.", "d443a8": "Changzhou Haojie Electric Co., Ltd.", @@ -30983,6 +31071,7 @@ "dc0d30": "Shenzhen Feasycom Technology Co., Ltd.", "dc0e96": "Palo Alto Networks", "dc0ea1": "CO., LTD. ", + "dc1057": "Apple, Inc.", "dc152d": "China Mobile Group Device Co.,Ltd.", "dc15c8": "AVM Audiovisuelles Marketing und Computersysteme GmbH", "dc15db": "Co., Ltd.", @@ -31077,6 +31166,7 @@ "dc58bc": "Thomas-Krenn.AG", "dc5e36": "Paterson Technology", "dc60a1": "Teledyne DALSA Professional Imaging", + "dc621f": "HUAWEI TECHNOLOGIES CO.,LTD", "dc6294": "Guangzhou Lango Electronics Technology Co.,Ltd.", "dc6373": "OBARA KOREA", "dc647c": "C.R.S. iiMotion GmbH", @@ -31148,6 +31238,7 @@ "dc9eab": "Chongqing Yipingfang Technology Co., Ltd.", "dc9fa4": "Nokia Corporation", "dc9fdb": "Ubiquiti Inc", + "dca0d0": "Amazon Technologies Inc.", "dca120": "Nokia", "dca266": "Hon Hai Precision Ind. Co.,Ltd.", "dca313": "Shenzhen Changjin Communication Technology Co.,Ltd", @@ -31919,6 +32010,7 @@ "e4f8ef": "Samsung Electronics Co.,Ltd", "e4f939": "Minxon Hotel Technology INC.", "e4fa1d": "PAD Peripheral Advanced Design Inc.", + "e4fa5b": "Texas Instruments", "e4fac4": "Big Field Global PTE. Ltd.", "e4faed": "Samsung Electronics Co.,Ltd", "e4fafd": "Intel Corporate", @@ -31985,6 +32077,7 @@ "e828d5": "Cots Technology", "e82a44": "Liteon Technology Corporation", "e82aea": "Intel Corporate", + "e82bc5": "Huawei Device Co., Ltd.", "e82c6d": "SmartRG, Inc.", "e82e0c": "NETINT Technologies Inc.", "e82e24": "Out of the Fog Research LLC", @@ -32031,6 +32124,7 @@ "e8516e": "TSMART Inc.", "e85177": "Qingdao Intelligent&Precise Electronics Co.,Ltd.", "e8519d": "Yeonhab Precision Co.,LTD", + "e8519e": "Guangzhou Shiyuan Electronic Technology Company Limited", "e85484": "NEO Information Systems Co., Ltd.", "e855b4": "SAI Technology Inc.", "e85659": "Advanced-Connectek Inc.", @@ -32163,6 +32257,7 @@ "e8b541": "zte corporation", "e8b5d0": "Dell Inc.", "e8b6c2": "Juniper Networks", + "e8b722": "GreenTrol Automation ", "e8b748": "Cisco Systems, Inc", "e8ba70": "Cisco Systems, Inc", "e8bae2": "Xplora Technologies AS", @@ -32256,6 +32351,7 @@ "e8f408": "Intel Corporate", "e8f654": "HUAWEI TECHNOLOGIES CO.,LTD", "e8f724": "Hewlett Packard Enterprise", + "e8f72f": "HUAWEI TECHNOLOGIES CO.,LTD", "e8f791": "Xiaomi Communications Co Ltd", "e8f8d0": "Nokia Shanghai Bell Co., Ltd.", "e8f928": "RFTECH SRL", @@ -32283,6 +32379,7 @@ "ec08e5": "Motorola Mobility LLC, a Lenovo Company", "ec0bae": "Hangzhou BroadLink Technology Co.,Ltd", "ec0c96": "Nokia", + "ec0d51": "Apple, Inc.", "ec0d9a": "Mellanox Technologies, Inc.", "ec0de4": "Amazon Technologies Inc.", "ec0ec4": "Hon Hai Precision Ind. Co.,Ltd.", @@ -32396,6 +32493,7 @@ "ec6c9a": "Arcadyan Corporation", "ec6c9f": "Chengdu Volans Technology CO.,LTD", "ec6cb5": "zte corporation", + "ec6e79": "InHand Networks, INC.", "ec6f0b": "FADU, Inc.", "ec7097": "ARRIS Group, Inc.", "ec71db": "Reolink Innovation Limited", @@ -32440,6 +32538,7 @@ "ec8ead": "DLX", "ec8eae": "Nagravision SA", "ec8eb5": "Hewlett Packard", + "ec9161": "CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD.", "ec9233": "Eddyfi NDT Inc", "ec9327": "MEMMERT GmbH + Co. KG", "ec9365": "Mapper.ai, Inc.", @@ -32454,6 +32553,7 @@ "ec97e0": "Hangzhou Ezviz Software Co.,Ltd.", "ec986c": "Lufft Mess- und Regeltechnik GmbH", "ec98c1": "Beijing Risbo Network Technology Co.,Ltd", + "ec9a34": "Texas Instruments", "ec9a74": "Hewlett Packard", "ec9b2d": "China Mobile Group Device Co.,Ltd.", "ec9b5b": "Nokia Corporation", @@ -32485,6 +32585,7 @@ "ecb0e1": "Ciena Corporation", "ecb106": "Acuro Networks, Inc", "ecb1d7": "Hewlett Packard", + "ecb1e0": "Eltex Enterprise LTD", "ecb313": "SHENZHEN GONGJIN ELECTRONICS CO.,LT", "ecb4e8": "Wistron Mexico SA de CV", "ecb541": "SHINANO E and E Co.Ltd.", @@ -32498,6 +32599,7 @@ "ecbd1d": "Cisco Systems, Inc", "ecbe5f": "Vestel Elektronik San ve Tic. A.S.", "ecbedd": "Sagemcom Broadband SAS", + "ecbfd0": "Texas Instruments", "ecc018": "Cisco Systems, Inc", "ecc01b": "HUAWEI TECHNOLOGIES CO.,LTD", "ecc06a": "PowerChord Group Limited", @@ -32627,6 +32729,7 @@ "f02e51": "Casa Systems", "f02f4b": "Apple, Inc.", "f02f74": "ASUSTek COMPUTER INC.", + "f02f9e": "Amazon Technologies Inc.", "f02fa7": "HUAWEI TECHNOLOGIES CO.,LTD", "f02fd8": "Bi2-Vision", "f0321a": "Mita-Teknik A/S", @@ -32882,6 +32985,7 @@ "f0f7b3": "Phorm", "f0f7e7": "HUAWEI TECHNOLOGIES CO.,LTD", "f0f842": "KEEBOX, Inc.", + "f0f84a": "BUFFALO.INC", "f0f8f2": "Texas Instruments", "f0f9f7": "IES GmbH & Co. KG", "f0fac7": "Huawei Device Co., Ltd.", @@ -33000,6 +33104,7 @@ "f44fd3": "shenzhen hemuwei technology co.,ltd", "f450eb": "Telechips Inc", "f45214": "Mellanox Technologies, Inc.", + "f45246": "ASKEY COMPUTER CORP", "f45420": "TELLESCOM INDUSTRIA E COMERCIO EM TELECOMUNICACAO ", "f45424": "Extreme Networks Headquarters", "f45433": "Rockwell Automation", @@ -33059,6 +33164,7 @@ "f47def": "Samsung Electronics Co.,Ltd", "f47f35": "Cisco Systems, Inc", "f48139": "CANON INC.", + "f4832c": "OHSUNG", "f483cd": "TP-LINK TECHNOLOGIES CO.,LTD.", "f483e1": "Shanghai Clouder Semiconductor Co.,Ltd", "f4844c": "Texas Instruments", @@ -33233,6 +33339,7 @@ "f8051c": "DRS Imaging and Targeting Solutions", "f8075d": "Huawei Device Co., Ltd.", "f8084f": "Sagemcom Broadband SAS", + "f809a4": "Henan Thinker Rail Transportation Research Inc.", "f80bbe": "ARRIS Group, Inc.", "f80bcb": "Cisco Systems, Inc", "f80bd0": "Co., Ltd.", @@ -33268,6 +33375,7 @@ "f81f32": "Motorola Mobility LLC, a Lenovo Company", "f82055": "Green Information System", "f820a9": "Huawei Device Co., Ltd.", + "f82111": "HMD Global Oy", "f82229": "Nokia Shanghai Bell Co., Ltd.", "f82285": "Cypress Technology CO., LTD.", "f82387": "Shenzhen Horn Audio Co.,Ltd.", @@ -33386,6 +33494,7 @@ "f86c03": "Shenzhen Teleone Technology Co., Ltd", "f86ce1": "Taicang T&W Electronics", "f86d73": "Zengge Co., Limited", + "f86dcc": "Wistron Neweb Corporation", "f86ecf": "Arcx Inc", "f86eee": "HUAWEI TECHNOLOGIES CO.,LTD", "f86fb0": "TP-LINK TECHNOLOGIES CO.,LTD.", @@ -33410,6 +33519,7 @@ "f87b62": "FASTWEL INTERNATIONAL CO., LTD. Taiwan Branch", "f87b7a": "ARRIS Group, Inc.", "f87b8c": "Amped Wireless", + "f87d3f": "Huawei Device Co., Ltd.", "f87d76": "Apple, Inc.", "f87fa5": "GREATEK", "f88096": "Elsys Equipamentos Eletrônicos Ltda", @@ -33480,6 +33590,7 @@ "f8abe5": "shenzhen worldelite electronics co., LTD", "f8ac65": "Intel Corporate", "f8ac6d": "Deltenna Ltd", + "f8acc1": "InnoXings Co., LTD.", "f8ad24": "Realme Chongqing Mobile Telecommunications Corp.,Ltd.", "f8adcb": "HMD Global Oy", "f8ae27": "John Deere Electronic Solutions", @@ -33709,6 +33820,7 @@ "fc500c": "Sitehop Ltd", "fc5090": "SIMEX Sp. z o.o.", "fc51a4": "ARRIS Group, Inc.", + "fc51b5": "HUAWEI TECHNOLOGIES CO.,LTD", "fc528d": "Vantiva USA LLC", "fc52ce": "Control iD", "fc539e": "Shanghai Wind Technologies Co.,Ltd", @@ -33819,6 +33931,7 @@ "fca84a": "Sentinum GmbH", "fca89a": "Sunitec Enterprise Co.,Ltd", "fca89b": "Texas Instruments", + "fca8e0": "THE FURUKAWA ELECTRIC CO., LTD", "fca9b0": "INC.", "fca9dc": "Sdn. Bhd.", "fca9f5": "Xiaomi Communications Co Ltd", From 22df4a670df2b17c9efcedc0dfe8ab6538b301f0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 10 Aug 2023 21:53:41 -0600 Subject: [PATCH 07/21] Flat: latest data (2023-08-10T08:05:51.145Z) (#338) { "date": "2023-08-10T08:05:51.145Z", "files": [ { "name": "netutils/data_files/protocol_mappings.py", "deltaBytes": 63, "source": "https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.csv" } ] } Co-authored-by: flat-data --- netutils/data_files/protocol_mappings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/netutils/data_files/protocol_mappings.py b/netutils/data_files/protocol_mappings.py index a78ccb75..282553fa 100644 --- a/netutils/data_files/protocol_mappings.py +++ b/netutils/data_files/protocol_mappings.py @@ -5409,6 +5409,7 @@ "pcsync-http": {"port_number": 8444, "protocols": ["tcp", "udp"]}, "copy": {"port_number": 8445, "protocols": ["tcp"]}, "copy-disc": {"port_number": 8445, "protocols": ["udp"]}, + "matrix-fed": {"port_number": 8448, "protocols": ["tcp"]}, "npmp": {"port_number": 8450, "protocols": ["tcp", "udp"]}, "nexentamv": {"port_number": 8457, "protocols": ["tcp"]}, "cisco-avp": {"port_number": 8470, "protocols": ["tcp"]}, From b4227a2105dc40e03a283c4b218545ca8789c1b4 Mon Sep 17 00:00:00 2001 From: Ken Celenza Date: Fri, 11 Aug 2023 00:05:37 -0400 Subject: [PATCH 08/21] Update spelling (#341) --- docs/user/lib_use_cases.md | 5 ++++- docs/user/lib_use_cases_protocol_mappers.md | 4 ++-- netutils/interface.py | 10 +++++----- netutils/password.py | 6 +++--- netutils/regex.py | 2 +- 5 files changed, 15 insertions(+), 12 deletions(-) diff --git a/docs/user/lib_use_cases.md b/docs/user/lib_use_cases.md index 2e01aa04..9a5d230c 100644 --- a/docs/user/lib_use_cases.md +++ b/docs/user/lib_use_cases.md @@ -6,12 +6,13 @@ As the Python library is intended to be a low-level implementation, the primary Functions are grouped with like functions, such as IP or MAC address based functions. Included to date are groupings of: +- ASN - Provides the ability to convert BGP ASN from integer to dot notation and back. - Bandwidth - Provides the ability to convert between various bandwidth values. - Banner - Provides the ability to normalize the various banner delimiters. -- BGP ASN - Provides the ability to convert BGP ASN from integer to dot notation. - Configuration - Cleaning - Provides the ability to remove or replace lines based on regex matches. - Compliance - Provides the ability to compare two configurations to sanely understand the differences. + - Conversion - Provides the ability to convert between different syntax's within the same OS. - Parsing - Provides the ability to parse configuration for the minor differences that are there. - DNS - Provides the ability to work with DNS, such as validating that a FQDN is resolvable. - Interface - Provides the ability to work with interface names, expanding, abbreviating, and splitting the names. @@ -19,9 +20,11 @@ Functions are grouped with like functions, such as IP or MAC address based funct - Library Helpers - Provides helpers to pull useful information, e.g. NAPALM getters. - Library Mapper - Provides mappings in expected vendor names between Netmiko, NAPALM, pyntc, ntc-templates, pyats, and scrapli. - MAC Address - Provides the ability to work with MAC addresses such as validating or converting to integer. +- OS Version - Provides the ability to work with OS version, such as defining an upgrade path. - Password - Provides the ability to compare and encrypt common password schemas such as type5 and type7 Cisco passwords. - Ping - Provides the ability to ping, currently only tcp ping. - Protocol Mapper - Provides a mapping for protocol names to numbers and vice versa. +- Regex - Provide convenience methods for regex to be used in Jinja2. - Route - Provides the ability to provide a list of routes and an IP Address and return the longest prefix matched route. - Time -Provides the ability to convert between integer time and string times. - VLANs - Provide the ability to convert configuration into lists or lists into configuration. diff --git a/docs/user/lib_use_cases_protocol_mappers.md b/docs/user/lib_use_cases_protocol_mappers.md index 747b4c4c..7c071de4 100644 --- a/docs/user/lib_use_cases_protocol_mappers.md +++ b/docs/user/lib_use_cases_protocol_mappers.md @@ -79,11 +79,11 @@ Take the below example. |compressnet| 3| tcp| Compression Process | |compressnet| 3| udp| Compression Process | -As you can see, the service name and port comboniation is not unique. As such, on the second one, a `-secondary` is appended to the name to accomdate. The resulting structure looks something like: +As you can see, the service name and port combination is not unique. As such, on the second one, a `-secondary` is appended to the name to accommodate. The resulting structure looks something like: ```python "compressnet": {"port_number": 2, "protocols": ["tcp", "udp"]}, "compressnet-secondary": {"port_number": 3, "protocols": ["tcp", "udp"]}, ``` -This behaviour is to be expected. \ No newline at end of file +This behavior is to be expected. \ No newline at end of file diff --git a/netutils/interface.py b/netutils/interface.py index 0c50625c..154aad8d 100644 --- a/netutils/interface.py +++ b/netutils/interface.py @@ -147,8 +147,8 @@ def canonical_interface_name_list( """Function to return a list of interface's canonical name (fully expanded name). Use of explicit matches used to indicate a clear understanding on any potential - match. Regex and other looser matching methods were not implmented to avoid false - positive matches. As an example, it would make sense to do "[P|p][O|o]" which would + match. Regex and other looser matching methods were not implemented to avoid false + positive matches. As an example, it would make sense to do `[P|p][O|o]` which would incorrectly match PO = POS and Po = Port-channel, leading to a false positive, not easily troubleshot, found, or known. @@ -420,7 +420,7 @@ def _iter_tree(node: t.Dict[CharacterClass, t.Any], parents: t.List[CharacterCla """Walk a tree of interface name parts. Weights are assigned based on domain logic to produce a - 'cannonical' ordering of names. + 'canonical' ordering of names. """ for _, items in itertools.groupby(sorted(node.keys()), lambda t: t.weight): for item in sorted(items): @@ -446,8 +446,8 @@ def sort_interface_list(interfaces: t.List[str]) -> t.List[str]: Examples: >>> sort_interface_list(["Gi1/0/1", "Gi1/0/3", "Gi1/0/3.100", "Gi1/0/2", "Gi1/0/2.50", "Gi2/0/2", "Po40", "Po160", "Lo10"]) ['Gi1/0/1', 'Gi1/0/2', 'Gi1/0/2.50', 'Gi1/0/3', 'Gi1/0/3.100', 'Gi2/0/2', 'Lo10', 'Po40', 'Po160'] - >>> sort_interface_list(['GigabitEthernet1/0/1', 'GigabitEthernet1/0/3', 'GigabitEthernet1/0/2', "GigabitEthernett3/0/5", 'GigabitEthernet3/0/7', 'GigabitEthernet2/0/8.5', 'Port-channel40', 'Vlan20', 'Loopback10']) - ['GigabitEthernet1/0/1', 'GigabitEthernet1/0/2', 'GigabitEthernet1/0/3', 'GigabitEthernet2/0/8.5', 'GigabitEthernet3/0/7', 'GigabitEthernett3/0/5', 'Loopback10', 'Port-channel40', 'Vlan20'] + >>> sort_interface_list(['GigabitEthernet1/0/1', 'GigabitEthernet1/0/3', 'GigabitEthernet1/0/2', "GigabitEthernet3/0/5", 'GigabitEthernet3/0/7', 'GigabitEthernet2/0/8.5', 'Port-channel40', 'Vlan20', 'Loopback10']) + ['GigabitEthernet1/0/1', 'GigabitEthernet1/0/2', 'GigabitEthernet1/0/3', 'GigabitEthernet2/0/8.5', 'GigabitEthernet3/0/5', 'GigabitEthernet3/0/7', 'Loopback10', 'Port-channel40', 'Vlan20'] """ root: t.Dict[CharacterClass, t.Any] = {} for ifname in interfaces: diff --git a/netutils/password.py b/netutils/password.py index 02f1bd86..5a9026a7 100644 --- a/netutils/password.py +++ b/netutils/password.py @@ -250,12 +250,12 @@ def encrypt_cisco_type5(unencrypted_password: str, salt: t.Optional[str] = None, if not salt: salt = "".join(secrets.choice(ALPHABET) for _ in range(salt_len)) elif not set(salt) <= set(ALPHABET): - raise ValueError(f"type5_pw salt used inproper characters, must be one of {ALPHABET}") + raise ValueError(f"type5_pw salt used improper characters, must be one of {ALPHABET}") return crypt.crypt(unencrypted_password, f"$1${salt}$") def encrypt_cisco_type7(unencrypted_password: str, salt: t.Optional[int] = None) -> str: - """Given an unencrypted password of Cisco Type 7 password, encypt it. + """Given an unencrypted password of Cisco Type 7 password, encrypt it. Args: unencrypted_password: A password that has not been encrypted, and will be compared against. @@ -397,7 +397,7 @@ def decrypt_juniper_type9(encrypted_password: str) -> str: nibble = stripped_password_characters[0 : len(decode)] # noqa: E203 stripped_password_characters = stripped_password_characters[len(decode) :] # noqa: E203 - # Decode value for nibble and convert to character, append to decryped password + # Decode value for nibble and convert to character, append to decrypted password value = 0 for index, char in enumerate(nibble): gap = ( diff --git a/netutils/regex.py b/netutils/regex.py index 67472ddb..cf851e76 100644 --- a/netutils/regex.py +++ b/netutils/regex.py @@ -109,7 +109,7 @@ def regex_search(pattern: str, string: str) -> t.Union[t.List[str], str, None]: def regex_split(pattern: str, string: str, maxsplit: int = 0) -> t.List[str]: - """Given a regex pattern and string, return the split the object based on the patern a single element or single element of original value if there is no match. + """Given a regex pattern and string, return the split the object based on the pattern a single element or single element of original value if there is no match. The main purpose of this function is provide a Jinja2 filter as this is simply a wrapper around `re.split`. From a98264e42d29cc327fd08fb332d161975e4f545e Mon Sep 17 00:00:00 2001 From: Justin Drew <2396364+jdrew82@users.noreply.github.com> Date: Mon, 14 Aug 2023 11:06:30 -0500 Subject: [PATCH 09/21] Correct Netscaler parser slug (#340) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 🚚 Rename slug used for Citrix Netscaler to align with netmiko driver To align with the other slugs used for platforms I've corrected it to use netscaler instead of citrix_netscaler. * test: Fix docs slug ordering for pytest * revert: ⏪️ Restore citrix_netscaler entry in parser_map --- docs/dev/include_parser_list.md | 1 + netutils/config/compliance.py | 3 ++- .../{citrix_netscaler => netscaler}/netscaler_basic_backup.txt | 0 .../{citrix_netscaler => netscaler}/netscaler_basic_feature.py | 0 .../netscaler_basic_intended.txt | 0 .../netscaler_basic_received.json | 0 .../{citrix_netscaler => netscaler}/netscaler_full_received.py | 0 .../{citrix_netscaler => netscaler}/netscaler_full_sent.txt | 0 8 files changed, 3 insertions(+), 1 deletion(-) rename tests/unit/mock/config/compliance/compliance/{citrix_netscaler => netscaler}/netscaler_basic_backup.txt (100%) rename tests/unit/mock/config/compliance/compliance/{citrix_netscaler => netscaler}/netscaler_basic_feature.py (100%) rename tests/unit/mock/config/compliance/compliance/{citrix_netscaler => netscaler}/netscaler_basic_intended.txt (100%) rename tests/unit/mock/config/compliance/compliance/{citrix_netscaler => netscaler}/netscaler_basic_received.json (100%) rename tests/unit/mock/config/parser/base/{citrix_netscaler => netscaler}/netscaler_full_received.py (100%) rename tests/unit/mock/config/parser/base/{citrix_netscaler => netscaler}/netscaler_full_sent.txt (100%) diff --git a/docs/dev/include_parser_list.md b/docs/dev/include_parser_list.md index 721a2255..3c025637 100644 --- a/docs/dev/include_parser_list.md +++ b/docs/dev/include_parser_list.md @@ -15,6 +15,7 @@ | linux | netutils.config.parser.LINUXConfigParser | | mikrotik_routeros | netutils.config.parser.RouterOSConfigParser | | mrv_optiswitch | netutils.config.parser.OptiswitchConfigParser | +| netscaler | netutils.config.parser.NetscalerConfigParser | | nokia_sros | netutils.config.parser.NokiaConfigParser | | paloalto_panos | netutils.config.parser.PaloAltoNetworksConfigParser | | ruckus_fastiron | netutils.config.parser.FastironConfigParser | \ No newline at end of file diff --git a/netutils/config/compliance.py b/netutils/config/compliance.py index 4cafc783..fc1370ed 100644 --- a/netutils/config/compliance.py +++ b/netutils/config/compliance.py @@ -12,13 +12,14 @@ "cisco_iosxr": parser.IOSXRConfigParser, "cisco_nxos": parser.NXOSConfigParser, "cisco_aireos": parser.AIREOSConfigParser, + "citrix_netscaler": parser.NetscalerConfigParser, "linux": parser.LINUXConfigParser, "bigip_f5": parser.F5ConfigParser, "juniper_junos": parser.JunosConfigParser, "cisco_asa": parser.ASAConfigParser, "fortinet_fortios": parser.FortinetConfigParser, "nokia_sros": parser.NokiaConfigParser, - "citrix_netscaler": parser.NetscalerConfigParser, + "netscaler": parser.NetscalerConfigParser, "aruba_aoscx": parser.ArubaConfigParser, "mrv_optiswitch": parser.OptiswitchConfigParser, "extreme_netiron": parser.NetironConfigParser, diff --git a/tests/unit/mock/config/compliance/compliance/citrix_netscaler/netscaler_basic_backup.txt b/tests/unit/mock/config/compliance/compliance/netscaler/netscaler_basic_backup.txt similarity index 100% rename from tests/unit/mock/config/compliance/compliance/citrix_netscaler/netscaler_basic_backup.txt rename to tests/unit/mock/config/compliance/compliance/netscaler/netscaler_basic_backup.txt diff --git a/tests/unit/mock/config/compliance/compliance/citrix_netscaler/netscaler_basic_feature.py b/tests/unit/mock/config/compliance/compliance/netscaler/netscaler_basic_feature.py similarity index 100% rename from tests/unit/mock/config/compliance/compliance/citrix_netscaler/netscaler_basic_feature.py rename to tests/unit/mock/config/compliance/compliance/netscaler/netscaler_basic_feature.py diff --git a/tests/unit/mock/config/compliance/compliance/citrix_netscaler/netscaler_basic_intended.txt b/tests/unit/mock/config/compliance/compliance/netscaler/netscaler_basic_intended.txt similarity index 100% rename from tests/unit/mock/config/compliance/compliance/citrix_netscaler/netscaler_basic_intended.txt rename to tests/unit/mock/config/compliance/compliance/netscaler/netscaler_basic_intended.txt diff --git a/tests/unit/mock/config/compliance/compliance/citrix_netscaler/netscaler_basic_received.json b/tests/unit/mock/config/compliance/compliance/netscaler/netscaler_basic_received.json similarity index 100% rename from tests/unit/mock/config/compliance/compliance/citrix_netscaler/netscaler_basic_received.json rename to tests/unit/mock/config/compliance/compliance/netscaler/netscaler_basic_received.json diff --git a/tests/unit/mock/config/parser/base/citrix_netscaler/netscaler_full_received.py b/tests/unit/mock/config/parser/base/netscaler/netscaler_full_received.py similarity index 100% rename from tests/unit/mock/config/parser/base/citrix_netscaler/netscaler_full_received.py rename to tests/unit/mock/config/parser/base/netscaler/netscaler_full_received.py diff --git a/tests/unit/mock/config/parser/base/citrix_netscaler/netscaler_full_sent.txt b/tests/unit/mock/config/parser/base/netscaler/netscaler_full_sent.txt similarity index 100% rename from tests/unit/mock/config/parser/base/citrix_netscaler/netscaler_full_sent.txt rename to tests/unit/mock/config/parser/base/netscaler/netscaler_full_sent.txt From 15df1ccbc7a374c51f9d7a62b839bb0f02c85c2c Mon Sep 17 00:00:00 2001 From: Ken Celenza Date: Thu, 17 Aug 2023 22:28:23 -0400 Subject: [PATCH 10/21] Acl check (#290) Co-authored-by: Jeff Kala <48843785+jeffkala@users.noreply.github.com> Co-authored-by: Adam Byczkowski <38091261+qduk@users.noreply.github.com> --- .bandit.yml | 2 +- .github/workflows/ci.yml | 6 +- docs/assets/acl-workflow.drawio | 367 +++++++++++++++ docs/dev/code_reference/acl.md | 5 + docs/dev/{arch_decision.md => dev_adr.md} | 0 docs/images/acl-workflow.png | Bin 0 -> 122856 bytes docs/user/include_jinja_list.md | 4 + docs/user/lib_use_cases.md | 2 +- docs/user/lib_use_cases_acl.md | 304 +++++++++++++ mkdocs.yml | 3 + netutils/acl.py | 532 ++++++++++++++++++++++ netutils/dns.py | 4 +- netutils/interface.py | 4 +- netutils/ip.py | 153 +++++++ netutils/utils.py | 4 + poetry.lock | 210 ++++++++- pyproject.toml | 3 +- tasks.py | 2 +- tests/unit/test_acl.py | 375 +++++++++++++++ tests/unit/test_ip.py | 167 +++++++ tests/unit/test_lib_helpers_optionals.py | 16 +- tests/unit/test_utils.py | 2 +- 22 files changed, 2136 insertions(+), 29 deletions(-) create mode 100644 docs/assets/acl-workflow.drawio create mode 100644 docs/dev/code_reference/acl.md rename docs/dev/{arch_decision.md => dev_adr.md} (100%) create mode 100644 docs/images/acl-workflow.png create mode 100644 docs/user/lib_use_cases_acl.md create mode 100644 netutils/acl.py create mode 100644 tests/unit/test_acl.py diff --git a/.bandit.yml b/.bandit.yml index ae781ceb..3bd46e2c 100644 --- a/.bandit.yml +++ b/.bandit.yml @@ -3,4 +3,4 @@ exclude_dirs: - "./tests/" - "./docs/" - - "netutils/oui_mappings.py" + - "netutils/data_files/" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 95628584..8bada118 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -204,10 +204,10 @@ jobs: run: "docker image ls" - name: "Run Tests" run: "poetry run invoke pytest" - - name: "Install Napalm" - run: "pip install napalm" + - name: "Install Optional Dependencies" + run: "poetry run poetry install --extras optionals" - name: "Run Optional Tests" - run: "poetry run pytest tests/unit/test_lib_helpers_optionals.py" + run: "poetry run pytest tests/unit/test_lib_helpers_optionals.py tests/unit/test_acl.py" needs: - "pylint" publish_gh: diff --git a/docs/assets/acl-workflow.drawio b/docs/assets/acl-workflow.drawio new file mode 100644 index 00000000..ef750b63 --- /dev/null +++ b/docs/assets/acl-workflow.drawio @@ -0,0 +1,367 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/dev/code_reference/acl.md b/docs/dev/code_reference/acl.md new file mode 100644 index 00000000..d13ca277 --- /dev/null +++ b/docs/dev/code_reference/acl.md @@ -0,0 +1,5 @@ +# ACLs + +::: netutils.acl + options: + show_submodules: True \ No newline at end of file diff --git a/docs/dev/arch_decision.md b/docs/dev/dev_adr.md similarity index 100% rename from docs/dev/arch_decision.md rename to docs/dev/dev_adr.md diff --git a/docs/images/acl-workflow.png b/docs/images/acl-workflow.png new file mode 100644 index 0000000000000000000000000000000000000000..d7a990eb6dad8c5bb3eeffe6e0e6d0f45d37b9d2 GIT binary patch literal 122856 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 z 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⩔8FOA^cOvF93S_hZP8sx=6S9r6E7PI-AIk?#cOY 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
<| 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 from netutils.acl import _cartesian_product +>>> rule = dict( +... name="Allow to internal web", +... src_ip=["192.168.0.0/24", "10.0.0.0/16"], +... dst_ip=["172.16.0.0/16", "192.168.250.10-192.168.250.20"], +... dst_port=["tcp/80", "udp/53"], +... action="permit", +... ) +>>> for item in _cartesian_product(rule): +... print(item) +... +{'name': 'Allow to internal web', 'src_ip': '192.168.0.0/24', 'dst_ip': '172.16.0.0/16', 'dst_port': 'tcp/80', 'action': 'permit'} +{'name': 'Allow to internal web', 'src_ip': '192.168.0.0/24', 'dst_ip': '172.16.0.0/16', 'dst_port': 'udp/53', 'action': 'permit'} +{'name': 'Allow to internal web', 'src_ip': '192.168.0.0/24', 'dst_ip': '192.168.250.10-192.168.250.20', 'dst_port': 'tcp/80', 'action': 'permit'} +{'name': 'Allow to internal web', 'src_ip': '192.168.0.0/24', 'dst_ip': '192.168.250.10-192.168.250.20', 'dst_port': 'udp/53', 'action': 'permit'} +{'name': 'Allow to internal web', 'src_ip': '10.0.0.0/16', 'dst_ip': '172.16.0.0/16', 'dst_port': 'tcp/80', 'action': 'permit'} +{'name': 'Allow to internal web', 'src_ip': '10.0.0.0/16', 'dst_ip': '172.16.0.0/16', 'dst_port': 'udp/53', 'action': 'permit'} +{'name': 'Allow to internal web', 'src_ip': '10.0.0.0/16', 'dst_ip': '192.168.250.10-192.168.250.20', 'dst_port': 'tcp/80', 'action': 'permit'} +{'name': 'Allow to internal web', 'src_ip': '10.0.0.0/16', 'dst_ip': '192.168.250.10-192.168.250.20', 'dst_port': 'udp/53', 'action': 'permit'} +>>> +``` + +Now that you have the Cartesian products, you can evaluate each one individually. In this example perhaps '192.168.0.0/24' -> '172.16.0.0/16' is allowed, but '192.168.0.0/24' -> '192.168.250.10-192.168.250.20' is denied. Yet another example is the access could be allowed on both sources and destinations IPs, but not for udp/53 (DNS). + +Having the ability to look at each product individually allows you to only have to worry about the check you wish to create, versus custom logic that attempts to understand the permutations. Building a `is_pci_to_non_pci` becomes trivial when looking at each product. This idea applies to validating, enforcing, matching, etc. + + +### Dynamic Method - Attrs + +The methods `process` and `match` both follow this pattern. As an example, the `process` method will dynamically find any method that follows `f"process_{attr}` pattern. This allows a Python class that inherits from `ACLRule` to simply add a `process_src_ip` method and that method would be called. + +### Dynamic Method - Any + +The methods `validate` and `enforce` both follow this pattern. As an example, the `validate` method will dynamically find any method that follows `f"process_*` pattern. This allows a Python class that inherits from `ACLRule` to simply add a `validate_ip_in_network` method and that method would be called. + +In both methods, ordering can be controlled with order_validate and order_enforce respectively. The default ordering will be alphabetical, as returned by the Python dir function. + +## ACLRule + +The `ACLRule` class at a high level: + +- The class `ACLRule` is used for working with Access Control List (ACL) rules. +- It is built with extensibility in mind to allow you to customize how your business operates. +- It contains a list of attributes such as "name," "src_ip," "src_zone," "dst_ip," "dst_port," "dst_zone," and "action" that are commonly used to work with ACLs. +- Provides the ability for you to expand data such as converting an address-group name into detailed addresses with your custom code. +- Provides the Cartesian product (or permutations) of the rules to make evaluation simple. +- It provides options for verifying input data and result data (data expanded) with corresponding JSON schemas. +- Provides the ability to validate data, generally for tech feasibility testing such as "are IPs on our network and in IPAM" or "is NAT IP provided vs actual IP". +- Provides the ability to enforce data, generally for security testing such as "is PCI attempting to talk with a non-PCI environment" or "is IP range not narrowly scoped enough". + - The class supports a matrix feature, which allows users to define custom rule matching based on predefined matrix definitions. +- Provides the ability to match one rule to another, to understand if rule exists already. + +### Initialization & Loading Data + +The initialization process calls on the `load_data` method. This on a high level verifies schema of initial data, allows you to process data (e.g. convert tcp/https -> 80/443), expand data, determine Cartesian product (or permutations) of the firewall rule (traditionally 5-tuple), and verifies schema of result data. + +The Cartesian product (or permutations) is key to the functionality of other steps, this allows you to evaluate each rule based on the smallest view of the data, so pay close attention to those steps, as it is important to other methods as well. + +To provide some ideas on what you may validate: + +- Is one of the source or destination IPs in our network? +- Is one of the source or destination IPs in our IPAM? +- Is the source and destination IPs able to route to each other? +- Is the source and destination IPs on the same network? +- Is one of the destination IPs to the real IP vs the NAT IP? +- Is there routing from the source zone to the destination zone? +- Is the business unit name in the description ? + +Many of validations will be based on IPs, but not all. + +Here you will find a written understanding of what is happening in the code: + +- The init method takes in data and calls `load_data`. +- The method `load_data` processes the input data. + - The `input_data_check` method is called and verifies the input data based on the specified JSON schema. + - This is controlled by the `input_data_verify` attribute and schema defined in `input_data_schema`. + - For each `self.attrs`, a method name matching `f"process_{attr}"`, (e.g. `process_src_ip()`) is called. + - This allows you to inherit from and provide your own custom processes to convert, expand, or otherwise modify data before being evaluated. + - The `process_dst_port` method processes the `dst_port` attribute by converting the protocol and port information, it is enabled by default but controlled with the `dst_port_process` attribute. + - Both a dictionary `self.processed` and attributes (e.g. self.action, self.src_ip, etc.) are created. + - The `result_data_check` method verifies the processed data based on the specified JSON schema. + - This is controlled by the `result_data_verify` attribute which is disabled by default. + - The `validate` method validating the rule using a series of custom methods starting with `validate_` prefixes. + - The ordering can be controlled based on the `order_validate` attribute or defaults to all matches. + - The rules are expanded into `self.expanded_rules` by creating each permutations of the tuple, using a Cartesian product function. + - An example permutations would be converting source: 10.1.1.1, 10.1.1.2, destination: 10.100.100.100, port: 80 -> 10.1.1.1, destination: 10.100.100.100, port: 80 and 10.1.1.2, destination: 10.100.100.100, port: 80. + - This will be key, so that each permutations can be compared individually later. + - Filter out the permutations that have the same source and destination IP, if `self.filter_same_ip` which is on by default. + +### Enforce + +Enforce is generally used for security controls. An `enforce_matrix` is provided but not used by default. You can think of the matrix as an Excel sheet, in which you have source as the rows and destination as the column. You would identify the source/destination IP and find which x & y coordinates in your Excel document, and perform whatever action it states, such as deny, review, approve, etc. + +To provide some ideas on what you may enforce: + +- Is the request for a PCI to non-PCI network? +- Is the request for a security-zone-X to security-zone-Y network? +- Is the protocol a secure protocol? +- Is the request approved? +- Is the IPs requested narrowly scoped? + +Here you will find a written understanding of what is happening in the code: + +- The `enforce` method validating the rule using a series of custom methods starting with `enforce_` prefixes such as `enforce_pci_checks`. + - The ordering can be controlled based on the `order_enforce` attribute or defaults to all matches. + - The `enforce_matrix` method enforces ACL rules based on a predefined matrix feature. + - This is controlled by the `self.matrix_enforced` attribute and is off by default. + - The `enforce_matrix` method runs the enforcement checks for each of the `self.expanded_rules` (or permutations of tuples) + - This matrix definition is very simple and not likely ready to be be used in a production environment, instead used for simple demonstrations and communicating potential ideas. + - Each method should return a dictionary or list of dictionaries as both of these are handled + - In the example there is the `obj` and `action` key. + - This could and should be extended, such as providing obj, action, detail_msg, notification_team, and any other metadata that the tooling using this system would require. + - Catastrophic errors will raise an error. + +While not accurate in all use cases it would be best practice to run any of your custom `enforce_` against `self.expanded_rules`. + +### Match & Match Details + +The `match_details` method provides a verbose way of verifying match details between two ACL rule's, the `match` method uses `match_details` and provides a boolean if there are any rules in `rules_unmatched` which would tell you if you had a full match or not. We will only review in detail the `match_details`. + +Here you will find a written understanding of what is happening in the code: + +- The `self.expanded_rules` is looped over for every permutations. + - For each `self.attrs`, a method name matching `f"match_{attr}"`, (e.g. `match_src_ip()`) is called. + - This allows you to inherit from and provide your own custom equality check or verify with your business logic. + - You do not need to have a `f"match_{attr}"` method for every attr, description as example would not be a good candidate to match on. + - Equality checks are done on `src_zone`, `dst_zone`, `action`, and `port` by default. + - An `is_ip_within` check is done with for `src_ip` and `dst_ip` by default. +- In the process, details are provided for and returned: + - `rules_matched` - Root key that is a list of dictionaries of rules that matched. + - `rules_unmatched` - Root key that is a list of dictionaries of rules that did not match. + - `existing_rule_product` - The original expanded_rule that existed in this item. + - `existing_rule` - The full original rule (not expanded_rule) that existed. + - `match_rule` - The full original rule that tested against, only shown in `rules_matched` root key. + +This data could help you to understand what matched, why it matched, and other metadata. This detail data can be used to in `ACLRules` to aggregate and ask more interesting questions. + +## ACLRules + +The `ACLRules` class at a high level: + +- Loads up multiple `ACLRule` and loads the data from a list of dictionaries. + - This is generally the data that exists on the firewall already, but there are other use cases. +- Allows you to match the multiple `ACLRule` objects, and test against a single `ACLRule` object. + - This is generally to see if the access to the rule you are testing exists already or not. + +Using the `match_details` method, you could as an example, build logic if every product of the rule is matched, just not against a single rule. This is one of many different ways you could use the data. + +## Example Usage + +Here we can test if a rule is matched via the existing ruleset. We can leverage the permit or deny to understand if this exists already or not. + +**Simple Example** + +```python +>>> from netutils.acl import ACLRules +>>> +>>> existing_acls = [ +... dict( +... name="Allow to internal web", +... src_ip=["192.168.0.0/24", "10.0.0.0/16"], +... dst_ip=["172.16.0.0/16", "192.168.250.10-192.168.250.20"], +... dst_port=["tcp/80", "udp/53"], +... action="permit", +... ), +... dict( +... name="Allow to internal dns", +... src_ip=["192.168.1.0/24"], +... dst_ip=["172.16.0.0/16"], +... dst_port=["tcp/80", "udp/53"], +... action="permit", +... ) +... ] +>>> +>>> new_acl_match = dict( +... name="Check multiple sources pass", +... src_ip=["192.168.1.10", "192.168.1.11", "192.168.1.15-192.168.1.20"], +... dst_ip="172.16.0.10", +... dst_port="tcp/www-http", +... action="permit", +... ) +>>> +>>> ACLRules(existing_acls).match(new_acl_match) +'permit' +>>> +>>> +>>> new_acl_non_match = dict( +... name="Check no match", +... src_ip=["10.1.1.1"], +... dst_ip="172.16.0.10", +... dst_port="tcp/www-http", +... action="permit", +... ) +>>> +>>> ACLRules(existing_acls).match(new_acl_non_match) +'deny' +>>> +``` + +**Inherit Example** + +```python + +from netutils.acl import ACLRule + +class ExpandAddrGroups(ACLRule): + address_groups = {"red": ["white", "blue"], "blue": ["cyan"], "yellow": ["orange"]} + addresses = {"white": ["10.1.1.1", "10.2.2.2"], "cyan": ["10.3.3.3"], "orange": ["10.4.4.4"]} + + def __init__(self, data, *args, **kwargs): + self.flattened_addresses = self.flatten_addresses(self.address_groups, self.addresses) + super().__init__(data, *args, **kwargs) + + def flatten_addresses(self, address_groups, addresses): + flattened_addresses = {} + for group, subgroups in address_groups.items(): + if group in addresses: + flattened_addresses.setdefault(group, []).extend(addresses[group]) + for subgroup in subgroups: + if subgroup in addresses: + flattened_addresses.setdefault(group, []).extend(addresses[subgroup]) + if subgroup in address_groups: + subgroup_addresses = self.flatten_addresses({subgroup: address_groups[subgroup]}, addresses) + for sub_group, ips in subgroup_addresses.items(): + flattened_addresses.setdefault(sub_group, []).extend(ips) + if group != sub_group: + flattened_addresses.setdefault(group, []).extend(ips) + return flattened_addresses + + def process_ip(self, ip): + if not isinstance(ip, list): + ip = [ip] + output = [] + for ip_name in ip: + if not ip_name[0].isalpha(): + output.append(ip_name) + elif self.addresses.get(ip_name): + output.extend(self.addresses[ip_name]) + elif self.flattened_addresses.get(ip_name): + output.extend(self.flattened_addresses[ip_name]) + return sorted(list(set(output))) + + def process_src_ip(self, src_ip): + return self.process_ip(src_ip) + + def process_dst_ip(self, dst_ip): + return self.process_ip(dst_ip) +``` + +Using the above object, we can test with: + +```python +>>> rule_data = dict( +... name="Check allow", +... src_ip=["red", "blue", "10.4.4.4"], +... dst_ip=["white"], +... dst_port="6/www-http", +... action="permit", +... ) +>>> +>>> address_object_expanded = ExpandAddrGroups(rule_data) +>>> for item in address_object_expanded.expanded_rules: +... print(item) +... +{'name': 'Check allow', 'src_ip': '10.1.1.1', 'dst_ip': '10.2.2.2', 'dst_port': '6/80', 'action': 'permit'} +{'name': 'Check allow', 'src_ip': '10.2.2.2', 'dst_ip': '10.1.1.1', 'dst_port': '6/80', 'action': 'permit'} +{'name': 'Check allow', 'src_ip': '10.3.3.3', 'dst_ip': '10.1.1.1', 'dst_port': '6/80', 'action': 'permit'} +{'name': 'Check allow', 'src_ip': '10.3.3.3', 'dst_ip': '10.2.2.2', 'dst_port': '6/80', 'action': 'permit'} +{'name': 'Check allow', 'src_ip': '10.4.4.4', 'dst_ip': '10.1.1.1', 'dst_port': '6/80', 'action': 'permit'} +{'name': 'Check allow', 'src_ip': '10.4.4.4', 'dst_ip': '10.2.2.2', 'dst_port': '6/80', 'action': 'permit'} +>>> +``` + +In that example you can see how we expanded `red` -> 10.1.1.1", "10.2.2.2", "10.3.3.3" as an example. diff --git a/mkdocs.yml b/mkdocs.yml index 3e8d9909..21297829 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -106,6 +106,7 @@ nav: - Jinja Filters: "user/lib_use_cases_jinja_filters.md" - Library Mapper: "user/lib_use_cases_lib_mapper.md" - Protocol Mapper: "user/lib_use_cases_protocol_mappers.md" + - ACL: "user/lib_use_cases_acl.md" - Upgrade Paths: "user/lib_upgrade_paths.md" - Getting Started: "user/lib_getting_started.md" - Frequently Asked Questions: "user/faq.md" @@ -128,10 +129,12 @@ nav: - Contributing to the Library: "dev/contributing.md" - Development Environment: "dev/dev_environment.md" - Config Parser Development: "dev/dev_config.md" + - Arch Decision Records: "dev/dev_adr.md" - Code Attribution to the Library: "dev/attribution.md" - Code Reference: - "dev/code_reference/index.md" - ASN: "dev/code_reference/asn.md" + - ACL: "dev/code_reference/acl.md" - Bandwidth: "dev/code_reference/bandwidth.md" - Banner: "dev/code_reference/banner.md" - Configs: "dev/code_reference/configs.md" diff --git a/netutils/acl.py b/netutils/acl.py new file mode 100644 index 00000000..88eeba66 --- /dev/null +++ b/netutils/acl.py @@ -0,0 +1,532 @@ +"""Classes to help manage ACLs .""" + +import itertools +import copy +import typing as t +from netutils.protocol_mapper import PROTO_NAME_TO_NUM, TCP_NAME_TO_NUM, UDP_NAME_TO_NUM +from netutils.ip import is_ip_within + +try: + import jsonschema + + HAS_JSON_SCHEMA = True +except ImportError: + HAS_JSON_SCHEMA = False + +INPUT_SCHEMA = { + "type": "object", + "properties": { + "name": {"type": "string"}, + "src_zone": {"type": ["string", "array"]}, + "src_ip": {"$ref": "#/definitions/arrayOrIP"}, + "dst_ip": {"$ref": "#/definitions/arrayOrIP"}, + "dst_port": { + "oneOf": [ + { + "$ref": "#/definitions/port", + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/port", + }, + "minItems": 1, + "uniqueItems": True, + }, + ], + }, + "dst_zone": {"type": ["string", "array"]}, + "action": {"type": "string"}, + }, + "definitions": { + "ipv4": {"type": "string", "pattern": "^(?:\\d{1,3}\\.){3}\\d{1,3}$"}, + "ipv4_cidr": {"type": "string", "pattern": "^(?:\\d{1,3}\\.){3}\\d{1,3}\\/\\d{1,2}$"}, + "ipv4_range": {"type": "string", "pattern": "^(?:\\d{1,3}\\.){3}\\d{1,3}\\-(?:\\d{1,3}\\.){3}\\d{1,3}$"}, + "ipv6": {"type": "string", "pattern": "^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$"}, + "ipv6_cidr": {"type": "string", "pattern": "^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}/[0-9]{1,3}$"}, + "ipv6_range": { + "type": "string", + "pattern": "^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}-([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$", + }, + "oneIP": { + "oneOf": [ + { + "$ref": "#/definitions/ipv4", + }, + { + "$ref": "#/definitions/ipv4_cidr", + }, + { + "$ref": "#/definitions/ipv4_range", + }, + { + "$ref": "#/definitions/ipv6", + }, + { + "$ref": "#/definitions/ipv6_cidr", + }, + { + "$ref": "#/definitions/ipv6_range", + }, + ], + }, + "arrayOrIP": { + "oneOf": [ + { + "$ref": "#/definitions/oneIP", + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/oneIP", + }, + }, + ], + }, + "port": {"type": "string", "pattern": "^\\S+\\/\\S+$"}, + }, + "required": [], +} + + +RESULT_SCHEMA = copy.deepcopy(INPUT_SCHEMA) +RESULT_SCHEMA["definitions"]["port"]["pattern"] = "^\\d+\\/\\d+$" # type: ignore + + +def _cartesian_product(data: t.Dict[str, str]) -> t.List[t.Dict[str, t.Any]]: + """Function to create the Cartesian product/permutations from a data dictionary. + + Args: + data: A dictionary with string keys and either string or list of string values + + Returns: + A list of dictionaries that extrapolates the product to individual items. + + Examples: + >>> from netutils.acl import _cartesian_product + >>> rule = dict( + ... name="Allow to internal web", + ... src_ip=["192.168.0.0/24", "10.0.0.0/16"], + ... dst_ip=["172.16.0.0/16", "192.168.250.10-192.168.250.20"], + ... dst_port=["tcp/80", "udp/53"], + ... action="permit", + ... ) + >>> for item in _cartesian_product(rule): + ... print(item) + ... + {'name': 'Allow to internal web', 'src_ip': '192.168.0.0/24', 'dst_ip': '172.16.0.0/16', 'dst_port': 'tcp/80', 'action': 'permit'} + {'name': 'Allow to internal web', 'src_ip': '192.168.0.0/24', 'dst_ip': '172.16.0.0/16', 'dst_port': 'udp/53', 'action': 'permit'} + {'name': 'Allow to internal web', 'src_ip': '192.168.0.0/24', 'dst_ip': '192.168.250.10-192.168.250.20', 'dst_port': 'tcp/80', 'action': 'permit'} + {'name': 'Allow to internal web', 'src_ip': '192.168.0.0/24', 'dst_ip': '192.168.250.10-192.168.250.20', 'dst_port': 'udp/53', 'action': 'permit'} + {'name': 'Allow to internal web', 'src_ip': '10.0.0.0/16', 'dst_ip': '172.16.0.0/16', 'dst_port': 'tcp/80', 'action': 'permit'} + {'name': 'Allow to internal web', 'src_ip': '10.0.0.0/16', 'dst_ip': '172.16.0.0/16', 'dst_port': 'udp/53', 'action': 'permit'} + {'name': 'Allow to internal web', 'src_ip': '10.0.0.0/16', 'dst_ip': '192.168.250.10-192.168.250.20', 'dst_port': 'tcp/80', 'action': 'permit'} + {'name': 'Allow to internal web', 'src_ip': '10.0.0.0/16', 'dst_ip': '192.168.250.10-192.168.250.20', 'dst_port': 'udp/53', 'action': 'permit'} + >>> + """ + keys = [] + values = [] + for key, value in data.items(): + keys.append(key) + if isinstance(value, (str, int)): + values.append([value]) + else: + values.append(value) + product = list(itertools.product(*values)) + return [dict(zip(keys, item)) for item in product] + + +def _check_schema(data: t.Any, schema: t.Any, verify: bool) -> None: + """Checks the schema provided.""" + if not verify: + return + if HAS_JSON_SCHEMA: + jsonschema.validate(data, schema) + else: + if not isinstance(data, dict): + raise ValueError() + + +class ACLRule: + """A class that helps you imagine an acl rule via methodologies.""" + + attrs: t.List[str] = ["name", "src_ip", "src_zone", "dst_ip", "dst_port", "dst_zone", "action"] + permit: str = "permit" + deny: str = "deny" + + input_data_verify: bool = False + input_data_schema: t.Any = INPUT_SCHEMA + + result_data_verify: bool = False + result_data_schema: t.Any = RESULT_SCHEMA + + matrix: t.Any = {} + matrix_enforced: bool = False + matrix_definition: t.Any = {} + + dst_port_process: bool = True + + order_validate: t.List[str] = [] + order_enforce: t.List[str] = [] + filter_same_ip: bool = True + + def __init__(self, data: t.Any, *args: t.Any, **kwargs: t.Any): # pylint: disable=unused-argument + """Initialize and load data. + + Args: + data: A dictionary with string keys and either string or list of string values + + Examples: + >>> from netutils.acl import ACLRule + >>> + >>> acl_data = dict( + ... name="Check no match", + ... src_ip=["10.1.1.1"], + ... dst_ip="172.16.0.10", + ... dst_port="tcp/www-http", + ... action="permit", + ... ) + >>> + >>> rule = ACLRule(acl_data) + >>> + >>> rule.expanded_rules + [{'name': 'Check no match', 'src_ip': '10.1.1.1', 'dst_ip': '172.16.0.10', 'dst_port': '6/80', 'action': 'permit'}] + >>> + """ + self.processed: t.Dict[str, str] = {} + self.data = data + self.load_data() + + def load_data(self) -> None: + """Load the data into the rule while verifying input data, result data, and processing data.""" + self.input_data_check() + for attr in self.attrs: + if not self.data.get(attr): + continue + if hasattr(self, f"process_{attr}"): + proccessor = getattr(self, f"process_{attr}") + _attr_data = proccessor(self.data[attr]) + else: + _attr_data = self.data[attr] + self.processed[attr] = _attr_data + setattr(self, attr, _attr_data) + self.result_data_check() + self.validate() + self.expanded_rules = _cartesian_product(self.processed) + if self.filter_same_ip: + self.expanded_rules = [item for item in self.expanded_rules if item["dst_ip"] != item["src_ip"]] + + def input_data_check(self) -> None: + """Verify the input data against the specified JSONSchema or using a simple dictionary check.""" + return _check_schema(self.data, self.input_data_schema, self.input_data_verify) + + def result_data_check(self) -> None: + """Verify the result data against the specified JSONSchema or using a simple dictionary check.""" + return _check_schema(self.processed, self.result_data_schema, self.result_data_verify) + + def validate(self) -> t.Any: + """Run through any method that startswith('validate_') and run that method.""" + if self.order_validate: + method_order = self.order_validate + else: + method_order = dir(self) + results = [] + for name in method_order: + if name.startswith("validate_"): + result = getattr(self, name)() + if not result: + continue + if result and isinstance(result, dict): + results.append(result) + elif result and isinstance(result, list): + results.extend(result) + return results + + def process_dst_port( + self, dst_port: t.Any + ) -> t.Union[t.List[str], None]: # pylint: disable=inconsistent-return-statements + """Convert port and protocol information. + + Method supports a single format of `{protocol}/{port}`, and will translate the + protocol for all IANA defined protocols. The port will be translated for TCP and + UDP ports only. For all other protocols should use port of 0, e.g. `ICMP/0` for ICMP + or `50/0` for ESP. Similarly, IANA defines the port mappings, while these are mostly + staying unchanged, but sourced from + https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.csv. + """ + output = [] + if not self.dst_port_process: + return None + if not isinstance(dst_port, list): + dst_port = [dst_port] + for item in dst_port: + protocol = item.split("/")[0] + port = item.split("/")[1] + if protocol.isalpha(): + if not PROTO_NAME_TO_NUM.get(protocol.upper()): + raise ValueError( + f"Protocol {protocol} was not found in netutils.protocol_mapper.PROTO_NAME_TO_NUM." + ) + protocol = PROTO_NAME_TO_NUM[protocol.upper()] + # test port[0] vs port, since dashes do not count, e.g. www-http + if int(protocol) == 6 and port[0].isalpha(): + if not TCP_NAME_TO_NUM.get(port.upper()): + raise ValueError(f"Port {port} was not found in netutils.protocol_mapper.TCP_NAME_TO_NUM.") + port = TCP_NAME_TO_NUM[port.upper()] + if int(protocol) == 17 and port[0].isalpha(): + if not UDP_NAME_TO_NUM.get(port.upper()): + raise ValueError(f"Port {port} was not found in netutils.protocol_mapper.UDP_NAME_TO_NUM.") + port = UDP_NAME_TO_NUM[port.upper()] + output.append(f"{protocol}/{port}") + return output + + def enforce(self) -> t.List[t.Dict[str, t.Any]]: + """Run through any method that startswith('enforce_') and run that method. + + Returns: + A list of dictionaries that explains the results of the enforcement. + """ + if self.order_enforce: + method_order = self.order_enforce + else: + method_order = dir(self) + results = [] + for name in method_order: + if name.startswith("enforce_"): + result = getattr(self, name)() + if not result: + continue + if result and isinstance(result, dict): + results.append(result) + elif result and isinstance(result, list): + results.extend(result) + return results + + def enforce_matrix(self) -> t.Union[t.List[t.Dict[str, t.Any]], None]: + """A simple `matrix` or grid style check of a rule. + + Returns: + A list of dictionaries that explains the results of the matrix being enforced. + """ + if not self.matrix_enforced: + return None + if not self.matrix: + raise ValueError("You must set a matrix dictionary to use the matrix feature.") + if not self.matrix_definition: + raise ValueError("You must set a matrix definition dictionary to use the matrix feature.") + actions = [] + for rule in self.expanded_rules: + source = rule["src_ip"] + destination = rule["dst_ip"] + port = rule["dst_port"] + src_zone = "" + dst_zone = "" + as_tuple = (source, destination, port) + for zone, ips in self.matrix_definition.items(): + if is_ip_within(source, ips): + src_zone = zone + if is_ip_within(destination, ips): + dst_zone = zone + if port in self.matrix.get(src_zone, {}).get(dst_zone, {}).get("allow", []): + actions.append({"obj": as_tuple, "action": "allow"}) + elif port in self.matrix.get(src_zone, {}).get(dst_zone, {}).get("notify", []): + actions.append({"obj": as_tuple, "action": "notify"}) + else: + actions.append({"obj": as_tuple, "action": "deny"}) + return actions + + def match_action(self, existing_action: str, check_action: str) -> bool: + """Match the action for equality. + + Args: + existing_action: The existing action value to be matched. + check_action: The action value to be checked against the existing action. + + Returns: + True if `existing_action` matches `check_action`, False otherwise. + """ + return existing_action == check_action + + def match_src_ip(self, existing_ip: str, check_ip: str) -> bool: + """Determined if source `check_ip` is within `existing_ip`. + + Args: + existing_ip: The existing source IP address or IP range to be matched against. + check_ip: The source IP address to be checked. + + Returns: + True if `check_ip` is within the range of `existing_ip`, False otherwise. + """ + return is_ip_within(check_ip, existing_ip) + + def match_src_zone(self, existing_src_zone: str, check_src_zone: str) -> bool: + """Match the source zone for equality. + + Args: + existing_src_zone: The existing source zone value to be matched. + check_src_zone: The source zone value to be checked against the existing zone. + + Returns: + True if `existing_src_zone` matches `check_src_zone`, False otherwise. + """ + return existing_src_zone == check_src_zone + + def match_dst_ip(self, existing_ip: str, check_ip: str) -> bool: + """Determined if destination `check_ip` is within `existing_ip. + + Args: + existing_ip: The existing destination IP address or IP range to be matched against. + check_ip: The destination IP address to be checked. + + Returns: + True if `check_ip` is within the range of `existing_ip`, False otherwise. + """ + return is_ip_within(check_ip, existing_ip) + + def match_dst_zone(self, existing_dst_zone: str, check_dst_zone: str) -> bool: + """Match the destination zone for equality. + + Args: + existing_dst_zone: The existing destination zone value to be matched. + check_dst_zone: The destination zone value to be checked against the existing zone. + + Returns: + True if `existing_dst_zone` matches `check_dst_zone`, False otherwise. + """ + return existing_dst_zone == check_dst_zone + + def match_dst_port(self, existing_port: str, check_port: str) -> bool: + """Match the destination port for equality. + + Args: + existing_port: The existing destination port value to be matched. + check_port: The destination port value to be checked against the existing port. + + Returns: + True if `existing_port` matches `check_port`, False otherwise. + """ + return existing_port == check_port + + def match_details(self, match_rule: "ACLRule") -> t.Dict[str, t.Any]: # pylint: disable=too-many-locals + """Verbose way of verifying match details. + + Args: + match_rule: The rule which you are testing against. + + Returns: + A dictionary with root keys of `rules_matched` and `rules_matched`. + """ + attrs = [] + for name in dir(self): + if name.startswith("match_"): + obj_name = name[len("match_") :] # noqa: E203 + # When an attribute is not defined, can skip it + if not hasattr(match_rule, obj_name): + continue + attrs.append(obj_name) + + rules_found: t.List[bool] = [] + rules_unmatched: t.List[t.Dict[str, t.Any]] = [] + rules_matched: t.List[t.Dict[str, t.Any]] = [] + + if not match_rule.expanded_rules: + raise ValueError("There is no expanded rules to test against.") + for rule in match_rule.expanded_rules: + rules_found.append(False) + for existing_rule in self.expanded_rules: + missing = False + for attr in attrs: + # Examples of obj are match_rule.src_ip, match_rule.dst_port + rule_value = rule[attr] + existing_value = existing_rule[attr] + # Examples of getter are self.match_src_ip, self.match_dst_port + getter = getattr(self, f"match_{attr}")(existing_value, rule_value) + if not getter and getter is not None: + missing = True + break + # If the loop gets through with each existing rule not flagging + # the `missing` value, we know everything was matched, and the rule has + # found a complete match, we can break out of the loop at this point. + if not missing: + rules_found[-1] = True + break + detailed_info = { + "existing_rule_product": existing_rule, # pylint: disable=undefined-loop-variable + "match_rule": match_rule.processed, + "existing_rule": self.processed, + } + if rules_found[-1]: + detailed_info["match_rule_product"] = rule + rules_matched.append(detailed_info) + else: + rules_unmatched.append(detailed_info) + return {"rules_matched": rules_matched, "rules_unmatched": rules_unmatched} + + def match(self, match_rule: "ACLRule") -> bool: + """Simple boolean way of verifying match or not. + + Args: + match_rule: The rule which you are testing against. + + Returns: + A boolean if there was a full match or not. + """ + details = self.match_details(match_rule) + return not bool(details["rules_unmatched"]) + + def __repr__(self) -> str: + """Set repr of the object to be sane.""" + output = [] + for attr in self.attrs: + if self.processed.get(attr): + output.append(f"{attr}: {self.processed[attr]}") + return ", ".join(output) + + +class ACLRules: + """Class to help match multiple ACLRule objects.""" + + class_obj = ACLRule + + def __init__(self, data: t.Any, *args: t.Any, **kwargs: t.Any): # pylint: disable=unused-argument + """Class to help match multiple ACLRule. + + Args: + data: A list of `ACLRule` rules. + """ + self.data: t.Any = data + self.rules: t.List[t.Any] = [] + self.load_data() + + def load_data(self) -> None: + """Load the data for multiple rules.""" + for item in self.data: + self.rules.append(self.class_obj(item)) + + def match(self, rule: ACLRule) -> str: + """Check the rules loaded in `load_data` match against a new `rule`. + + Args: + rule: The `ACLRule` rule to test against the list of `ACLRule`s loaded in initiation. + + Returns: + The response from the rule that matched, or `deny` by default. + """ + for item in self.rules: + if item.match(self.class_obj(rule)): + return str(item.action) + return str(item.deny) # pylint: disable=undefined-loop-variable + + def match_details(self, rule: ACLRule) -> t.Any: + """Verbosely check the rules loaded in `load_data` match against a new `rule`. + + Args: + rule: The `ACLRule` rule to test against the list of `ACLRule`s loaded in initiation. + + Returns: + The details from the `ACLRule.match_details` results. + """ + output = [] + for item in self.rules: + output.append(item.match_details(self.class_obj(rule))) + return output diff --git a/netutils/dns.py b/netutils/dns.py index 965ae879..8eb2b9c1 100644 --- a/netutils/dns.py +++ b/netutils/dns.py @@ -35,10 +35,10 @@ def is_fqdn_resolvable(hostname: str) -> bool: from your machine to the DNS server, an upstream DNS issue, etc. Args: - hostname (str): A FQDN that may or may not be resolvable. + hostname: A FQDN that may or may not be resolvable. Returns: - bool: The result as to whether or not the domain was valid. + The result as to whether or not the domain was valid. Examples: >>> from netutils.dns import is_fqdn_resolvable diff --git a/netutils/interface.py b/netutils/interface.py index 154aad8d..ea18fe76 100644 --- a/netutils/interface.py +++ b/netutils/interface.py @@ -438,10 +438,10 @@ def sort_interface_list(interfaces: t.List[str]) -> t.List[str]: nodes are removed. Args: - interfaces (list[str]): A list of interfaces to be sorted. The input list is not mutated by this function. + interfaces: A list of interfaces to be sorted. The input list is not mutated by this function. Returns: - list[str]: A **new** sorted, unique list elements from the input. + A **new** sorted, unique list elements from the input. Examples: >>> sort_interface_list(["Gi1/0/1", "Gi1/0/3", "Gi1/0/3.100", "Gi1/0/2", "Gi1/0/2.50", "Gi2/0/2", "Po40", "Po160", "Lo10"]) diff --git a/netutils/ip.py b/netutils/ip.py index afbe91a2..7926e3c5 100644 --- a/netutils/ip.py +++ b/netutils/ip.py @@ -5,6 +5,8 @@ from netutils.constants import IPV4_MASKS, IPV6_MASKS +IPAddress = t.Union[ipaddress.IPv4Address, ipaddress.IPv6Address] + def ipaddress_address(ip: str, attr: str) -> t.Any: """Convenience function primarily built to expose ipaddress.ip_address to Jinja. @@ -236,6 +238,106 @@ def is_ip(ip: str) -> bool: return False +def is_ip_range(ip_range: str) -> bool: + """Verifies whether or not a string is a valid IP address range. + + An `ip_range` is in the format of `{ip_start}-{ip_end}`, IPs in str format, same IP version, and + ip_start is before ip_end. + + Args: + ip_range: An IP address range in string format that is properly formatted. + + Returns: + The result as to whether or not the string is a valid IP address. + + Examples: + >>> from netutils.ip import is_ip_range + >>> is_ip_range("10.100.100.255") + False + >>> is_ip_range("10.100.100.255-10.100.101.1") + True + >>> + """ + if "-" not in ip_range: + return False + start_ip, end_ip = ip_range.split("-") + if not is_ip(start_ip) or not is_ip(end_ip): + return False + start_ip_obj = ipaddress.ip_address(start_ip) + end_ip_obj = ipaddress.ip_address(end_ip) + if not isinstance(start_ip_obj, type(end_ip_obj)): + return False + # IP version being the same is enforced above, mypy disagrees, can safely ignore + if not start_ip_obj < end_ip_obj: # type: ignore + return False + return True + + +def is_ip_within(ip: str, ip_compare: t.Union[str, t.List[str]]) -> bool: + """ + Check if an IP address, IP subnet, or IP range is within the range of a list of IP addresses, IP subnets, and IP ranges. + + Args: + ip: IP address, IP subnet, or IP range to check. + ip_compare: String or list of IP addresses, IP subnets, and IP ranges to compare against. + + Returns: + True if the IP is in range, False otherwise. + + Examples: + >>> from netutils.ip import is_ip_within + >>> is_ip_within("10.100.100.10", "10.100.100.0/24") + True + >>> is_ip_within("10.100.100.0/25", ["10.100.100.0/24", "10.100.200.0/24"]) + True + >>> + >>> is_ip_within("10.100.100.10", ["10.100.100.8-10.100.100.20", "10.100.200.0/24"]) + True + >>> is_ip_within("10.100.100.8-10.100.100.20", ["10.100.100.0/24"]) + True + >>> is_ip_within("1.1.1.1", ["2.2.2.2", "3.3.3.3"]) + False + >>> + """ + + def _convert_ip(ip: str) -> str: + if is_ip(ip): + if "." in ip: + mask = "32" + if ":" in ip: + mask = "128" + return f"{ip}/{mask}" + return ip + + if "-" in ip: + ip_obj_start, ip_obj_end = get_range_ips(ip) + else: + ip_obj = ipaddress.ip_network(_convert_ip(ip)) + ip_obj_start = ip_obj[0] + ip_obj_end = ip_obj[-1] + + if isinstance(ip_compare, str): + ip_compare = [ip_compare] + + for item in ip_compare: + if "-" in item: + item_obj_start, item_obj_end = get_range_ips(item) + + else: + item_obj = ipaddress.ip_network(_convert_ip(item)) + item_obj_start = item_obj[0] + item_obj_end = item_obj[-1] + if not isinstance(item_obj_start, type(item_obj_end)): + raise ValueError( + f"IP range start `{item_obj_start}` and end `{item_obj_end}` IPs must both be same IPVersion." + ) + # Use this validation method, since it is consitent with ranges + # vs the `.subnet_of` method which is not. + if item_obj_start <= ip_obj_start <= ip_obj_end <= item_obj_end: # type: ignore + return True + return False + + def is_netmask(netmask: str) -> bool: """Verifies whether or not a string is a valid subnet mask. @@ -260,6 +362,32 @@ def is_netmask(netmask: str) -> bool: return False +def is_network(ip_network: str) -> bool: + """Verifies whether or not a string is a valid IP Network with a Mask. + + Args: + ip: An IP network in string format that is able to be converted by `ipaddress` library. + + Returns: + The result as to whether or not the string is a valid IP network. + + Examples: + >>> from netutils.ip import is_network + >>> is_network("10.100.100.0") + False + >>> is_network("10.100.100.0/24") + True + >>> + """ + if "/" not in ip_network: + return False + try: + ipaddress.ip_network(ip_network) + return True + except ValueError: + return False + + def netmask_to_cidr(netmask: str) -> int: """Creates a CIDR notation of a given subnet mask in decimal format. @@ -420,6 +548,31 @@ def get_peer_ip(ip_interface: str) -> str: return val[0] +def get_range_ips(ip_range: str) -> t.Tuple[IPAddress, IPAddress]: + """Get's the two IPs as a tuple of IPAddress objects. + + Args: + ip_range: An IP address range in string format that is properly formatted. + + Returns: + The start and end IP address of the range provided. + + Examples: + >>> from netutils.ip import get_range_ips + >>> get_range_ips("10.100.100.255-10.100.101.1") + (IPv4Address('10.100.100.255'), IPv4Address('10.100.101.1')) + >>> get_range_ips("2001::1-2001::10") + (IPv6Address('2001::1'), IPv6Address('2001::10')) + >>> + """ + if not is_ip_range(ip_range): + raise ValueError(r"Not a valid IP range format of `{start_ip}-{end_ip}`") + start_ip, end_ip = ip_range.split("-") + start_ip_obj = ipaddress.ip_address(start_ip) + end_ip_obj = ipaddress.ip_address(end_ip) + return start_ip_obj, end_ip_obj + + def get_usable_range(ip_network: str) -> str: """Given a network, return the string of usable IP addresses. diff --git a/netutils/utils.py b/netutils/utils.py index 9eefd912..5d60c72e 100644 --- a/netutils/utils.py +++ b/netutils/utils.py @@ -35,7 +35,10 @@ "ip_subtract": "ip.ip_subtract", "is_classful": "ip.is_classful", "is_ip": "ip.is_ip", + "is_ip_range": "ip.is_ip_range", + "is_ip_within": "ip.is_ip_within", "is_netmask": "ip.is_netmask", + "is_network": "ip.is_network", "netmask_to_cidr": "ip.netmask_to_cidr", "cidr_to_netmask": "ip.cidr_to_netmask", "cidr_to_netmaskv6": "ip.cidr_to_netmaskv6", @@ -43,6 +46,7 @@ "get_broadcast_address": "ip.get_broadcast_address", "get_first_usable": "ip.get_first_usable", "get_peer_ip": "ip.get_peer_ip", + "get_range_ips": "ip.get_range_ips", "get_usable_range": "ip.get_usable_range", "ipaddress_address": "ip.ipaddress_address", "ipaddress_interface": "ip.ipaddress_interface", diff --git a/poetry.lock b/poetry.lock index 6d441dfc..eede6c71 100644 --- a/poetry.lock +++ b/poetry.lock @@ -45,6 +45,24 @@ files = [ six = ">=1.6.1,<2.0" wheel = ">=0.23.0,<1.0" +[[package]] +name = "attrs" +version = "23.1.0" +description = "Classes Without Boilerplate" +optional = true +python-versions = ">=3.7" +files = [ + {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"}, + {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"}, +] + +[package.extras] +cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] +dev = ["attrs[docs,tests]", "pre-commit"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] +tests = ["attrs[tests-no-zope]", "zope-interface"] +tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] + [[package]] name = "babel" version = "2.12.1" @@ -640,6 +658,24 @@ docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker perf = ["ipython"] testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] +[[package]] +name = "importlib-resources" +version = "6.0.1" +description = "Read resources from Python packages" +optional = true +python-versions = ">=3.8" +files = [ + {file = "importlib_resources-6.0.1-py3-none-any.whl", hash = "sha256:134832a506243891221b88b4ae1213327eea96ceb4e407a00d790bb0626f45cf"}, + {file = "importlib_resources-6.0.1.tar.gz", hash = "sha256:4359457e42708462b9626a04657c6208ad799ceb41e5c58c57ffa0e6a098a5d4"}, +] + +[package.dependencies] +zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff"] + [[package]] name = "iniconfig" version = "2.0.0" @@ -696,6 +732,44 @@ MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] +[[package]] +name = "jsonschema" +version = "4.19.0" +description = "An implementation of JSON Schema validation for Python" +optional = true +python-versions = ">=3.8" +files = [ + {file = "jsonschema-4.19.0-py3-none-any.whl", hash = "sha256:043dc26a3845ff09d20e4420d6012a9c91c9aa8999fa184e7efcfeccb41e32cb"}, + {file = "jsonschema-4.19.0.tar.gz", hash = "sha256:6e1e7569ac13be8139b2dd2c21a55d350066ee3f80df06c608b398cdc6f30e8f"}, +] + +[package.dependencies] +attrs = ">=22.2.0" +importlib-resources = {version = ">=1.4.0", markers = "python_version < \"3.9\""} +jsonschema-specifications = ">=2023.03.6" +pkgutil-resolve-name = {version = ">=1.3.10", markers = "python_version < \"3.9\""} +referencing = ">=0.28.4" +rpds-py = ">=0.7.1" + +[package.extras] +format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"] +format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "uri-template", "webcolors (>=1.11)"] + +[[package]] +name = "jsonschema-specifications" +version = "2023.7.1" +description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" +optional = true +python-versions = ">=3.8" +files = [ + {file = "jsonschema_specifications-2023.7.1-py3-none-any.whl", hash = "sha256:05adf340b659828a004220a9613be00fa3f223f2b82002e273dee62fd50524b1"}, + {file = "jsonschema_specifications-2023.7.1.tar.gz", hash = "sha256:c91a50404e88a1f6ba40636778e2ee08f6e24c5613fe4c53ac24578a5a7f72bb"}, +] + +[package.dependencies] +importlib-resources = {version = ">=1.4.0", markers = "python_version < \"3.9\""} +referencing = ">=0.28.0" + [[package]] name = "junos-eznc" version = "2.6.7" @@ -1351,6 +1425,17 @@ files = [ {file = "pbr-5.11.1.tar.gz", hash = "sha256:aefc51675b0b533d56bb5fd1c8c6c0522fe31896679882e1c4c63d5e4a0fccb3"}, ] +[[package]] +name = "pkgutil-resolve-name" +version = "1.3.10" +description = "Resolve a name to an object." +optional = true +python-versions = ">=3.6" +files = [ + {file = "pkgutil_resolve_name-1.3.10-py3-none-any.whl", hash = "sha256:ca27cc078d25c5ad71a9de0a7a330146c4e014c2462d9af19c6b828280649c5e"}, + {file = "pkgutil_resolve_name-1.3.10.tar.gz", hash = "sha256:357d6c9e6a755653cfd78893817c0853af365dd51ec97f3d358a819373bbd174"}, +] + [[package]] name = "platformdirs" version = "3.9.1" @@ -1670,6 +1755,21 @@ files = [ [package.dependencies] pyyaml = "*" +[[package]] +name = "referencing" +version = "0.30.2" +description = "JSON Referencing + Python" +optional = true +python-versions = ">=3.8" +files = [ + {file = "referencing-0.30.2-py3-none-any.whl", hash = "sha256:449b6669b6121a9e96a7f9e410b245d471e8d48964c67113ce9afe50c8dd7bdf"}, + {file = "referencing-0.30.2.tar.gz", hash = "sha256:794ad8003c65938edcdbc027f1933215e0d0ccc0291e3ce20a4d87432b59efc0"}, +] + +[package.dependencies] +attrs = ">=22.2.0" +rpds-py = ">=0.7.0" + [[package]] name = "requests" version = "2.31.0" @@ -1710,6 +1810,112 @@ typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.9 [package.extras] jupyter = ["ipywidgets (>=7.5.1,<9)"] +[[package]] +name = "rpds-py" +version = "0.9.2" +description = "Python bindings to Rust's persistent data structures (rpds)" +optional = true +python-versions = ">=3.8" +files = [ + {file = "rpds_py-0.9.2-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:ab6919a09c055c9b092798ce18c6c4adf49d24d4d9e43a92b257e3f2548231e7"}, + {file = "rpds_py-0.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d55777a80f78dd09410bd84ff8c95ee05519f41113b2df90a69622f5540c4f8b"}, + {file = "rpds_py-0.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a216b26e5af0a8e265d4efd65d3bcec5fba6b26909014effe20cd302fd1138fa"}, + {file = "rpds_py-0.9.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:29cd8bfb2d716366a035913ced99188a79b623a3512292963d84d3e06e63b496"}, + {file = "rpds_py-0.9.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:44659b1f326214950a8204a248ca6199535e73a694be8d3e0e869f820767f12f"}, + {file = "rpds_py-0.9.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:745f5a43fdd7d6d25a53ab1a99979e7f8ea419dfefebcab0a5a1e9095490ee5e"}, + {file = "rpds_py-0.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a987578ac5214f18b99d1f2a3851cba5b09f4a689818a106c23dbad0dfeb760f"}, + {file = "rpds_py-0.9.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bf4151acb541b6e895354f6ff9ac06995ad9e4175cbc6d30aaed08856558201f"}, + {file = "rpds_py-0.9.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:03421628f0dc10a4119d714a17f646e2837126a25ac7a256bdf7c3943400f67f"}, + {file = "rpds_py-0.9.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:13b602dc3e8dff3063734f02dcf05111e887f301fdda74151a93dbbc249930fe"}, + {file = "rpds_py-0.9.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fae5cb554b604b3f9e2c608241b5d8d303e410d7dfb6d397c335f983495ce7f6"}, + {file = "rpds_py-0.9.2-cp310-none-win32.whl", hash = "sha256:47c5f58a8e0c2c920cc7783113df2fc4ff12bf3a411d985012f145e9242a2764"}, + {file = "rpds_py-0.9.2-cp310-none-win_amd64.whl", hash = "sha256:4ea6b73c22d8182dff91155af018b11aac9ff7eca085750455c5990cb1cfae6e"}, + {file = "rpds_py-0.9.2-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:e564d2238512c5ef5e9d79338ab77f1cbbda6c2d541ad41b2af445fb200385e3"}, + {file = "rpds_py-0.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f411330a6376fb50e5b7a3e66894e4a39e60ca2e17dce258d53768fea06a37bd"}, + {file = "rpds_py-0.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e7521f5af0233e89939ad626b15278c71b69dc1dfccaa7b97bd4cdf96536bb7"}, + {file = "rpds_py-0.9.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8d3335c03100a073883857e91db9f2e0ef8a1cf42dc0369cbb9151c149dbbc1b"}, + {file = "rpds_py-0.9.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d25b1c1096ef0447355f7293fbe9ad740f7c47ae032c2884113f8e87660d8f6e"}, + {file = "rpds_py-0.9.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a5d3fbd02efd9cf6a8ffc2f17b53a33542f6b154e88dd7b42ef4a4c0700fdad"}, + {file = "rpds_py-0.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c5934e2833afeaf36bd1eadb57256239785f5af0220ed8d21c2896ec4d3a765f"}, + {file = "rpds_py-0.9.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:095b460e117685867d45548fbd8598a8d9999227e9061ee7f012d9d264e6048d"}, + {file = "rpds_py-0.9.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:91378d9f4151adc223d584489591dbb79f78814c0734a7c3bfa9c9e09978121c"}, + {file = "rpds_py-0.9.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:24a81c177379300220e907e9b864107614b144f6c2a15ed5c3450e19cf536fae"}, + {file = "rpds_py-0.9.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:de0b6eceb46141984671802d412568d22c6bacc9b230174f9e55fc72ef4f57de"}, + {file = "rpds_py-0.9.2-cp311-none-win32.whl", hash = "sha256:700375326ed641f3d9d32060a91513ad668bcb7e2cffb18415c399acb25de2ab"}, + {file = "rpds_py-0.9.2-cp311-none-win_amd64.whl", hash = "sha256:0766babfcf941db8607bdaf82569ec38107dbb03c7f0b72604a0b346b6eb3298"}, + {file = "rpds_py-0.9.2-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:b1440c291db3f98a914e1afd9d6541e8fc60b4c3aab1a9008d03da4651e67386"}, + {file = "rpds_py-0.9.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0f2996fbac8e0b77fd67102becb9229986396e051f33dbceada3debaacc7033f"}, + {file = "rpds_py-0.9.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f30d205755566a25f2ae0382944fcae2f350500ae4df4e795efa9e850821d82"}, + {file = "rpds_py-0.9.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:159fba751a1e6b1c69244e23ba6c28f879a8758a3e992ed056d86d74a194a0f3"}, + {file = "rpds_py-0.9.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a1f044792e1adcea82468a72310c66a7f08728d72a244730d14880cd1dabe36b"}, + {file = "rpds_py-0.9.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9251eb8aa82e6cf88510530b29eef4fac825a2b709baf5b94a6094894f252387"}, + {file = "rpds_py-0.9.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01899794b654e616c8625b194ddd1e5b51ef5b60ed61baa7a2d9c2ad7b2a4238"}, + {file = "rpds_py-0.9.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0c43f8ae8f6be1d605b0465671124aa8d6a0e40f1fb81dcea28b7e3d87ca1e1"}, + {file = "rpds_py-0.9.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:207f57c402d1f8712618f737356e4b6f35253b6d20a324d9a47cb9f38ee43a6b"}, + {file = "rpds_py-0.9.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b52e7c5ae35b00566d244ffefba0f46bb6bec749a50412acf42b1c3f402e2c90"}, + {file = "rpds_py-0.9.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:978fa96dbb005d599ec4fd9ed301b1cc45f1a8f7982d4793faf20b404b56677d"}, + {file = "rpds_py-0.9.2-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:6aa8326a4a608e1c28da191edd7c924dff445251b94653988efb059b16577a4d"}, + {file = "rpds_py-0.9.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:aad51239bee6bff6823bbbdc8ad85136c6125542bbc609e035ab98ca1e32a192"}, + {file = "rpds_py-0.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4bd4dc3602370679c2dfb818d9c97b1137d4dd412230cfecd3c66a1bf388a196"}, + {file = "rpds_py-0.9.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dd9da77c6ec1f258387957b754f0df60766ac23ed698b61941ba9acccd3284d1"}, + {file = "rpds_py-0.9.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:190ca6f55042ea4649ed19c9093a9be9d63cd8a97880106747d7147f88a49d18"}, + {file = "rpds_py-0.9.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:876bf9ed62323bc7dcfc261dbc5572c996ef26fe6406b0ff985cbcf460fc8a4c"}, + {file = "rpds_py-0.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa2818759aba55df50592ecbc95ebcdc99917fa7b55cc6796235b04193eb3c55"}, + {file = "rpds_py-0.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9ea4d00850ef1e917815e59b078ecb338f6a8efda23369677c54a5825dbebb55"}, + {file = "rpds_py-0.9.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:5855c85eb8b8a968a74dc7fb014c9166a05e7e7a8377fb91d78512900aadd13d"}, + {file = "rpds_py-0.9.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:14c408e9d1a80dcb45c05a5149e5961aadb912fff42ca1dd9b68c0044904eb32"}, + {file = "rpds_py-0.9.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:65a0583c43d9f22cb2130c7b110e695fff834fd5e832a776a107197e59a1898e"}, + {file = "rpds_py-0.9.2-cp38-none-win32.whl", hash = "sha256:71f2f7715935a61fa3e4ae91d91b67e571aeb5cb5d10331ab681256bda2ad920"}, + {file = "rpds_py-0.9.2-cp38-none-win_amd64.whl", hash = "sha256:674c704605092e3ebbbd13687b09c9f78c362a4bc710343efe37a91457123044"}, + {file = "rpds_py-0.9.2-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:07e2c54bef6838fa44c48dfbc8234e8e2466d851124b551fc4e07a1cfeb37260"}, + {file = "rpds_py-0.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f7fdf55283ad38c33e35e2855565361f4bf0abd02470b8ab28d499c663bc5d7c"}, + {file = "rpds_py-0.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:890ba852c16ace6ed9f90e8670f2c1c178d96510a21b06d2fa12d8783a905193"}, + {file = "rpds_py-0.9.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:50025635ba8b629a86d9d5474e650da304cb46bbb4d18690532dd79341467846"}, + {file = "rpds_py-0.9.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:517cbf6e67ae3623c5127206489d69eb2bdb27239a3c3cc559350ef52a3bbf0b"}, + {file = "rpds_py-0.9.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0836d71ca19071090d524739420a61580f3f894618d10b666cf3d9a1688355b1"}, + {file = "rpds_py-0.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c439fd54b2b9053717cca3de9583be6584b384d88d045f97d409f0ca867d80f"}, + {file = "rpds_py-0.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f68996a3b3dc9335037f82754f9cdbe3a95db42bde571d8c3be26cc6245f2324"}, + {file = "rpds_py-0.9.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7d68dc8acded354c972116f59b5eb2e5864432948e098c19fe6994926d8e15c3"}, + {file = "rpds_py-0.9.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:f963c6b1218b96db85fc37a9f0851eaf8b9040aa46dec112611697a7023da535"}, + {file = "rpds_py-0.9.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5a46859d7f947061b4010e554ccd1791467d1b1759f2dc2ec9055fa239f1bc26"}, + {file = "rpds_py-0.9.2-cp39-none-win32.whl", hash = "sha256:e07e5dbf8a83c66783a9fe2d4566968ea8c161199680e8ad38d53e075df5f0d0"}, + {file = "rpds_py-0.9.2-cp39-none-win_amd64.whl", hash = "sha256:682726178138ea45a0766907957b60f3a1bf3acdf212436be9733f28b6c5af3c"}, + {file = "rpds_py-0.9.2-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:196cb208825a8b9c8fc360dc0f87993b8b260038615230242bf18ec84447c08d"}, + {file = "rpds_py-0.9.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:c7671d45530fcb6d5e22fd40c97e1e1e01965fc298cbda523bb640f3d923b387"}, + {file = "rpds_py-0.9.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83b32f0940adec65099f3b1c215ef7f1d025d13ff947975a055989cb7fd019a4"}, + {file = "rpds_py-0.9.2-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7f67da97f5b9eac838b6980fc6da268622e91f8960e083a34533ca710bec8611"}, + {file = "rpds_py-0.9.2-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:03975db5f103997904c37e804e5f340c8fdabbb5883f26ee50a255d664eed58c"}, + {file = "rpds_py-0.9.2-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:987b06d1cdb28f88a42e4fb8a87f094e43f3c435ed8e486533aea0bf2e53d931"}, + {file = "rpds_py-0.9.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c861a7e4aef15ff91233751619ce3a3d2b9e5877e0fcd76f9ea4f6847183aa16"}, + {file = "rpds_py-0.9.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:02938432352359805b6da099c9c95c8a0547fe4b274ce8f1a91677401bb9a45f"}, + {file = "rpds_py-0.9.2-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:ef1f08f2a924837e112cba2953e15aacfccbbfcd773b4b9b4723f8f2ddded08e"}, + {file = "rpds_py-0.9.2-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:35da5cc5cb37c04c4ee03128ad59b8c3941a1e5cd398d78c37f716f32a9b7f67"}, + {file = "rpds_py-0.9.2-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:141acb9d4ccc04e704e5992d35472f78c35af047fa0cfae2923835d153f091be"}, + {file = "rpds_py-0.9.2-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:79f594919d2c1a0cc17d1988a6adaf9a2f000d2e1048f71f298b056b1018e872"}, + {file = "rpds_py-0.9.2-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:a06418fe1155e72e16dddc68bb3780ae44cebb2912fbd8bb6ff9161de56e1798"}, + {file = "rpds_py-0.9.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b2eb034c94b0b96d5eddb290b7b5198460e2d5d0c421751713953a9c4e47d10"}, + {file = "rpds_py-0.9.2-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8b08605d248b974eb02f40bdcd1a35d3924c83a2a5e8f5d0fa5af852c4d960af"}, + {file = "rpds_py-0.9.2-pp38-pypy38_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a0805911caedfe2736935250be5008b261f10a729a303f676d3d5fea6900c96a"}, + {file = "rpds_py-0.9.2-pp38-pypy38_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ab2299e3f92aa5417d5e16bb45bb4586171c1327568f638e8453c9f8d9e0f020"}, + {file = "rpds_py-0.9.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c8d7594e38cf98d8a7df25b440f684b510cf4627fe038c297a87496d10a174f"}, + {file = "rpds_py-0.9.2-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8b9ec12ad5f0a4625db34db7e0005be2632c1013b253a4a60e8302ad4d462afd"}, + {file = "rpds_py-0.9.2-pp38-pypy38_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:1fcdee18fea97238ed17ab6478c66b2095e4ae7177e35fb71fbe561a27adf620"}, + {file = "rpds_py-0.9.2-pp38-pypy38_pp73-musllinux_1_2_i686.whl", hash = "sha256:933a7d5cd4b84f959aedeb84f2030f0a01d63ae6cf256629af3081cf3e3426e8"}, + {file = "rpds_py-0.9.2-pp38-pypy38_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:686ba516e02db6d6f8c279d1641f7067ebb5dc58b1d0536c4aaebb7bf01cdc5d"}, + {file = "rpds_py-0.9.2-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:0173c0444bec0a3d7d848eaeca2d8bd32a1b43f3d3fde6617aac3731fa4be05f"}, + {file = "rpds_py-0.9.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:d576c3ef8c7b2d560e301eb33891d1944d965a4d7a2eacb6332eee8a71827db6"}, + {file = "rpds_py-0.9.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed89861ee8c8c47d6beb742a602f912b1bb64f598b1e2f3d758948721d44d468"}, + {file = "rpds_py-0.9.2-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1054a08e818f8e18910f1bee731583fe8f899b0a0a5044c6e680ceea34f93876"}, + {file = "rpds_py-0.9.2-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99e7c4bb27ff1aab90dcc3e9d37ee5af0231ed98d99cb6f5250de28889a3d502"}, + {file = "rpds_py-0.9.2-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c545d9d14d47be716495076b659db179206e3fd997769bc01e2d550eeb685596"}, + {file = "rpds_py-0.9.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9039a11bca3c41be5a58282ed81ae422fa680409022b996032a43badef2a3752"}, + {file = "rpds_py-0.9.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fb39aca7a64ad0c9490adfa719dbeeb87d13be137ca189d2564e596f8ba32c07"}, + {file = "rpds_py-0.9.2-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:2d8b3b3a2ce0eaa00c5bbbb60b6713e94e7e0becab7b3db6c5c77f979e8ed1f1"}, + {file = "rpds_py-0.9.2-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:99b1c16f732b3a9971406fbfe18468592c5a3529585a45a35adbc1389a529a03"}, + {file = "rpds_py-0.9.2-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:c27ee01a6c3223025f4badd533bea5e87c988cb0ba2811b690395dfe16088cfe"}, + {file = "rpds_py-0.9.2.tar.gz", hash = "sha256:8d70e8f14900f2657c249ea4def963bed86a29b81f81f5b76b5a9215680de945"}, +] + [[package]] name = "scp" version = "0.14.5" @@ -2254,9 +2460,9 @@ docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.link testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] [extras] -optionals = ["napalm"] +optionals = ["jsonschema", "napalm"] [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "aadbc2775614f7bea9461525b0490a12c6358995cca11489aed3a38c8b31ef9f" +content-hash = "ba0a3c213ba10e4eedf9c4fdc73090f89d1ac86face85422c546c5db5f1816b2" diff --git a/pyproject.toml b/pyproject.toml index fda0f3db..47cd4d71 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,9 +27,10 @@ include = [ [tool.poetry.dependencies] python = "^3.8" napalm = {version = "^4.0.0", optional = true} +jsonschema = {version = "^4.17.3", optional = true} [tool.poetry.extras] -optionals = ["napalm"] +optionals = ["jsonschema", "napalm"] [tool.poetry.dev-dependencies] bandit = "*" diff --git a/tasks.py b/tasks.py index cdbb3296..045025af 100644 --- a/tasks.py +++ b/tasks.py @@ -178,7 +178,7 @@ def pylint(context, local=INVOKE_LOCAL): context (obj): Used to run specific commands local (bool): Define as `True` to execute locally """ - exec_cmd = 'find . -name "*.py" | grep -v "tests/unit/mock" | xargs pylint' + exec_cmd = 'find . -name "*.py" | grep -vE "(tests/unit/mock|netutils/data_files)" | xargs pylint' run_cmd(context, exec_cmd, local) diff --git a/tests/unit/test_acl.py b/tests/unit/test_acl.py new file mode 100644 index 00000000..b7ce7f1f --- /dev/null +++ b/tests/unit/test_acl.py @@ -0,0 +1,375 @@ +"""Test for the BGP ASN functions.""" +# pylint: disable=use-dict-literal + +import pytest + +from netutils import acl + +verify_acl = [ + { + "sent": dict( + name="Check multiple sources pass. Check convesion of non-alpha tcp, e.g. with a dash", + src_ip=["192.168.1.10", "192.168.1.11", "192.168.1.15-192.168.1.20"], + dst_ip="172.16.0.10", + dst_port="tcp/www-http", + action="permit", + ), + "received": "permit", + }, + { + "sent": dict( + name="Check with number in port definition", + src_ip="192.168.0.10", + dst_ip="192.168.250.11", + dst_port="6/80", + action="permit", + ), + "received": "permit", + }, + { + "sent": dict( + name="Check with subnets", + src_ip="192.168.0.0/25", + dst_ip="172.16.0.0/24", + dst_port="6/80", + action="permit", + ), + "received": "permit", + }, + { + "sent": dict( + name="Test partial match on Source IP", + src_ip=["192.168.1.10", "192.168.2.10"], + dst_ip="172.16.0.11", + dst_port="tcp/80", + action="permit", + ), + "received": "deny", + }, + { + "sent": dict( + name="Test an entry that is not found", + src_ip="192.168.1.10", + dst_ip="192.168.240.1", + dst_port="tcp/80", + action="permit", + ), + "received": "deny", + }, + { + "sent": dict( + name="Test an action not permit or deny", + src_ip="10.1.1.1", + dst_ip="10.255.255.255", + dst_port="tcp/443", + action="permit", + ), + "received": "deny", + }, +] + +acls = [ + dict( + name="Allow to internal web", + src_ip=["192.168.0.0/24", "10.0.0.0/16"], + dst_ip=["172.16.0.0/16", "192.168.250.10-192.168.250.20"], + dst_port=["tcp/80", "udp/53"], + action="permit", + ), + dict( + name="Allow to internal dns", + src_ip=["192.168.1.0/24"], + dst_ip=["172.16.0.0/16"], + dst_port=["tcp/80", "udp/53"], + action="permit", + ), + dict( + name="Allow to internal https", + src_ip=["10.0.0.0/8"], + dst_ip=["172.16.0.0/16"], + dst_port=["tcp/443"], + action="deny", + ), + dict( + name="Drop (not deny) this specfic packet", + src_ip="10.1.1.1", + dst_ip="10.255.255.255", + dst_port="tcp/443", + action="drop", + ), + dict( + name="Allow External DNS", + src_ip=["0.0.0.0/0"], + dst_ip=["8.8.8.8/32", "8.8.4.4/32"], + dst_port=["udp/53"], + action="permit", + ), +] + +verify_matrix = [ + { + "sent": dict( + name="Check allow", + src_ip="10.1.100.5", + dst_ip="10.1.200.0", + dst_port="tcp/www-http", + action="permit", + ), + "received": [{"obj": ("10.1.100.5", "10.1.200.0", "6/80"), "action": "allow"}], + }, + { + "sent": dict( + name="Check Notify", + src_ip="10.1.100.5", + dst_ip="10.1.200.0", + dst_port="tcp/25", + action="permit", + ), + "received": [{"obj": ("10.1.100.5", "10.1.200.0", "6/25"), "action": "notify"}], + }, + { + "sent": dict( + name="Check not found and denied", + src_ip="10.1.100.5", + dst_ip="10.1.200.0", + dst_port="tcp/53", + action="permit", + ), + "received": [{"obj": ("10.1.100.5", "10.1.200.0", "6/53"), "action": "deny"}], + }, + { + "sent": dict( + name="Check not found and denied", + src_ip=["10.1.100.5", "10.1.100.6"], + dst_ip="10.1.200.0", + dst_port="tcp/80", + action="permit", + ), + "received": [ + {"obj": ("10.1.100.5", "10.1.200.0", "6/80"), "action": "allow"}, + {"obj": ("10.1.100.6", "10.1.200.0", "6/80"), "action": "allow"}, + ], + }, + { + "sent": dict( + name="Nothing found", + src_ip="1.1.1.1", + dst_ip="2.2.2.2", + dst_port="tcp/53", + action="permit", + ), + "received": [{"obj": ("1.1.1.1", "2.2.2.2", "6/53"), "action": "deny"}], + }, +] + +verify_schema = [ + { + "sent": dict( + name="Bad IP", + src_ip="10.1.100.A", + dst_ip="10.1.200.0", + dst_port="tcp/www-http", + action="permit", + ), + }, + { + "sent": dict( + name="Bad port", + src_ip="10.1.100.5", + dst_ip="10.1.200.0", + dst_port="tcp25", + action="permit", + ), + }, + { + "sent": dict( + name="Bad IP in list", + src_ip=["10.1.100.5", "10.1.100.A"], + dst_ip="10.1.200.0", + dst_port="tcp/25", + action="permit", + ), + }, +] + +verify_schema2 = [ + { + "sent": dict( + name="Check allow", + src_ip="10.1.100.1", + dst_ip="10.1.200.0", + dst_port="6/www-http", + action=100, + ), + }, +] + +IP_DEFINITIONS = { + "red": ["10.1.100.0/23", "10.1.102.0/24"], + "blue": ["10.1.200.0/23", "10.1.202.0/24"], + "orange": ["10.1.0.0/23", "10.1.2.0/24"], +} + +MATRIX = { + "red": {"blue": {"allow": ["6/80", "6/443"], "notify": ["6/25"]}, "orange": {"allow": ["6/80"]}}, + "blue": {"red": {"allow": ["6/80"]}}, +} + + +class TestMatrix(acl.ACLRule): + """ACLRule inherited class to test the matrix.""" + + matrix = MATRIX + matrix_enforced = True + matrix_definition = IP_DEFINITIONS + + +class TestSchema(acl.ACLRule): + """ACLRule inherited class to test the schema.""" + + input_data_verify = True + + +class TestSchema2(acl.ACLRule): + """ACLRule inherited class alternate to test the schema.""" + + result_data_verify = True + + +@pytest.mark.parametrize("data", verify_acl) +def test_verify_acl(data): + assert acl.ACLRules(acls).match(data["sent"]) == data["received"] + + +@pytest.mark.parametrize("data", verify_matrix) +def test_matrix(data): + assert TestMatrix(data["sent"]).enforce() == data["received"] + + +@pytest.mark.parametrize("data", verify_schema) +def test_schema(data): + pytest.importorskip("jsonschema") + try: + import jsonschema # pylint: disable=import-outside-toplevel + except ImportError: + pass + + with pytest.raises(jsonschema.exceptions.ValidationError): + TestSchema(data["sent"]) + + +def test_schema_not_enforced_when_option_not_set(): + try: + acl.ACLRule(dict(src_ip="10.1.1.1", dst_ip="10.2.2.2", dst_port="tcp/80", action=100)) + except Exception: # pylint: disable=broad-exception-caught + assert False, "No error should have been raised" + + +def test_schema_valid(): + try: + TestSchema(dict(src_ip="10.1.1.1", dst_ip="10.2.2.2", dst_port="tcp/80", action="permit")) + except Exception: # pylint: disable=broad-exception-caught + assert False, "No error should have been raised" + + +@pytest.mark.parametrize("data", verify_schema2) +def test_schema2(data): + pytest.importorskip("jsonschema") + try: + import jsonschema # pylint: disable=import-outside-toplevel + except ImportError: + pass + + with pytest.raises(jsonschema.exceptions.ValidationError): + TestSchema2(data["sent"]).validate() + + +def test_schema2_valid(): + try: + TestSchema2(dict(src_ip="10.1.1.1", dst_ip="10.2.2.2", dst_port="tcp/80", action="permit")).validate() + except Exception: # pylint: disable=broad-exception-caught + assert False, "No error should have been raised" + + +class TestAddrGroups(acl.ACLRule): + """ACLRule inherited class alternate to test expansions.""" + + address_groups = {"red": ["white", "blue"], "blue": ["cyan"], "yellow": ["orange"]} + + addresses = {"white": ["10.1.1.1", "10.2.2.2"], "cyan": ["10.3.3.3"], "orange": ["10.4.4.4"]} + + def __init__(self, data, *args, **kwargs): + self.flattened_addresses = self.flatten_addresses(self.address_groups, self.addresses) + super().__init__(data, *args, **kwargs) + + def flatten_addresses(self, address_groups, addresses): + """Go through and get the addresses given potential address groups.""" + + flattened_addresses = {} + + for group, subgroups in address_groups.items(): + if group in addresses: + flattened_addresses.setdefault(group, []).extend(addresses[group]) + for subgroup in subgroups: + if subgroup in addresses: + flattened_addresses.setdefault(group, []).extend(addresses[subgroup]) + if subgroup in address_groups: + subgroup_addresses = self.flatten_addresses({subgroup: address_groups[subgroup]}, addresses) + for sub_group, ips in subgroup_addresses.items(): + flattened_addresses.setdefault(sub_group, []).extend(ips) + if group != sub_group: + flattened_addresses.setdefault(group, []).extend(ips) + + return flattened_addresses + + def process_ip(self, ip): + """Test ability to expand IP for both source and destination.""" + + if not isinstance(ip, list): + ip = [ip] + output = [] + for ip_name in ip: + if not ip_name[0].isalpha(): + output.append(ip_name) + elif self.addresses.get(ip_name): + output.extend(self.addresses[ip_name]) + elif self.flattened_addresses.get(ip_name): + output.extend(self.flattened_addresses[ip_name]) + return sorted(list(set(output))) + + def process_src_ip(self, src_ip): + """Test ability to expand IP for both source.""" + + return self.process_ip(src_ip) + + def process_dst_ip(self, dst_ip): + """Test ability to expand IP for both destination.""" + + return self.process_ip(dst_ip) + + +add_group_check = [ + { + "sent": dict( + name="Check allow", + src_ip=["red", "blue", "10.4.4.4"], + dst_ip=["white"], + dst_port="6/www-http", + action="permit", + ), + "received": [ + {"action": "permit", "dst_ip": "10.2.2.2", "dst_port": "6/80", "name": "Check allow", "src_ip": "10.1.1.1"}, + {"action": "permit", "dst_ip": "10.1.1.1", "dst_port": "6/80", "name": "Check allow", "src_ip": "10.2.2.2"}, + {"action": "permit", "dst_ip": "10.1.1.1", "dst_port": "6/80", "name": "Check allow", "src_ip": "10.3.3.3"}, + {"action": "permit", "dst_ip": "10.2.2.2", "dst_port": "6/80", "name": "Check allow", "src_ip": "10.3.3.3"}, + {"action": "permit", "dst_ip": "10.1.1.1", "dst_port": "6/80", "name": "Check allow", "src_ip": "10.4.4.4"}, + {"action": "permit", "dst_ip": "10.2.2.2", "dst_port": "6/80", "name": "Check allow", "src_ip": "10.4.4.4"}, + ], + } +] + + +@pytest.mark.parametrize("data", add_group_check) +def test_custom_address_group(data): + obj = TestAddrGroups(data["sent"]) + assert obj.expanded_rules == data["received"] diff --git a/tests/unit/test_ip.py b/tests/unit/test_ip.py index 442fe22d..f3898382 100644 --- a/tests/unit/test_ip.py +++ b/tests/unit/test_ip.py @@ -1,4 +1,5 @@ """Test for the IP functions.""" +import ipaddress import pytest from netutils import ip @@ -160,6 +161,145 @@ }, ] +IS_IP_RANGE = [ + { + "sent": { + "ip_range": "10.1.1.1", + }, + "received": False, + }, + { + "sent": { + "ip_range": "10.1.100.10-10.1.100.1", + }, + "received": False, + }, + { + "sent": { + "ip_range": "2001::10-2001::1", + }, + "received": False, + }, + { + "sent": { + "ip_range": "10.500.100.10-10.1.100.1", + }, + "received": False, + }, + { + "sent": { + "ip_range": "NOT AN IP", + }, + "received": False, + }, + { + "sent": { + "ip_range": "255.255.255.256", + }, + "received": False, + }, + { + "sent": { + "ip_range": "10.1.100.10-10.1.100.100", + }, + "received": True, + }, + { + "sent": { + "ip_range": "2001::10-2001::100", + }, + "received": True, + }, +] + +GET_RANGE_IPS = [ + { + "sent": { + "ip_range": "2001::10-2001::100", + }, + "received": (ipaddress.IPv6Address("2001::10"), ipaddress.IPv6Address("2001::100")), + }, + { + "sent": { + "ip_range": "10.1.100.10-10.1.100.100", + }, + "received": (ipaddress.IPv4Address("10.1.100.10"), ipaddress.IPv4Address("10.1.100.100")), + }, +] + +IS_IP_WITHIN = [ + { + "sent": { + "ip": "192.168.1.10", + "ip_compare": "192.168.1.10", + }, + "received": True, + }, + { + "sent": { + "ip": "192.168.1.10", + "ip_compare": "192.168.1.0-192.168.1.20", + }, + "received": True, + }, + { + "sent": { + "ip": "192.168.1.0/24", + "ip_compare": ["192.168.1.0-192.168.1.20", "192.168.2.0-192.168.2.20"], + }, + "received": False, + }, + { + "sent": { + "ip": "192.168.1.10-192.168.1.15", + "ip_compare": ["192.168.1.0-192.168.1.20", "192.168.2.0-192.168.2.20"], + }, + "received": True, + }, + { + "sent": { + "ip": "10.0.0.0/8", + "ip_compare": ["192.168.1.0/24", "172.16.0.0/12"], + }, + "received": False, + }, + { + "sent": { + "ip": "192.168.1.10", + "ip_compare": "192.168.1.20", + }, + "received": False, + }, + { + "sent": { + "ip": "192.168.1.10", + "ip_compare": "192.168.2.0-192.168.2.20", + }, + "received": False, + }, + { + "sent": { + "ip": "192.168.1.0/24", + "ip_compare": ["192.168.2.0-192.168.2.20", "192.168.3.0-192.168.3.20"], + }, + "received": False, + }, + { + "sent": { + "ip": "192.168.1.50-192.168.1.60", + "ip_compare": ["192.168.2.0-192.168.2.20", "192.168.3.0-192.168.3.20"], + }, + "received": False, + }, + { + "sent": { + "ip": "192.168.1.10", + "ip_compare": "192.168.2.0/24", + }, + "received": False, + }, +] + GET_BROADCAST_ADDRESS = [ { "sent": {"ip_network": "10.1.1.0/24"}, @@ -357,6 +497,33 @@ def test_is_ip(data): assert ip.is_ip(**data["sent"]) == data["received"] +@pytest.mark.parametrize("data", IS_IP_RANGE) +def test_is_ip_range(data): + assert ip.is_ip_range(**data["sent"]) == data["received"] + + +@pytest.mark.parametrize("data", GET_RANGE_IPS) +def test_get_range_ips(data): + assert ip.get_range_ips(**data["sent"]) == data["received"] + + +def test_get_range_ips_fail(): + with pytest.raises(ValueError, match=r"Not a valid IP range format of .*"): + data = {"ip_range": "10.1.100.10-10.1.100.1"} + ip.get_range_ips(**data) + + +@pytest.mark.parametrize("data", IS_IP_WITHIN) +def test_is_ip_within(data): + assert ip.is_ip_within(**data["sent"]) == data["received"] + + +def test_is_ip_within_fail(): + with pytest.raises(ValueError): + data = {"ip": "10.1.100.100", "ip_compare": "10.1.100.10-2001::1"} + ip.is_ip_within(**data) + + @pytest.mark.parametrize("data", GET_ALL_HOST) def test_get_all_host(data): assert list(ip.get_all_host(**data["sent"])) == data["received"] diff --git a/tests/unit/test_lib_helpers_optionals.py b/tests/unit/test_lib_helpers_optionals.py index f383a16b..9d829966 100755 --- a/tests/unit/test_lib_helpers_optionals.py +++ b/tests/unit/test_lib_helpers_optionals.py @@ -9,21 +9,7 @@ def test_get_napalm_getters_napalm_installed_default(): pytest.importorskip("napalm") with mock.patch("netutils.lib_helpers.get_network_driver"): napalm_getters = get_napalm_getters() - assert napalm_getters == { - "asa": {}, - "cisco_wlc_ssh": {}, - "eos": {}, - "fortios": {}, - "huawei": {}, - "ios": {}, - "iosxr": {}, - "junos": {}, - "nxos": {}, - "nxos_ssh": {}, - "panos": {}, - "sros": {}, - "vyos": {}, - } + assert all(item in napalm_getters.keys() for item in ["asa", "eos", "fortios"]) def test_get_napalm_getters_napalm_installed_nxos_keys(): diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index 50d17983..c0e7028a 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -52,7 +52,7 @@ def get_jinja2_function_names(): def test_jinja2_mapping_contains_all_functions(get_jinja2_function_names): # pylint: disable=redefined-outer-name mapping_function_names = [path.split(".")[-1] for path in list(_JINJA2_FUNCTION_MAPPINGS.values())] - sorted_get_jinja2_function_names = sorted(get_jinja2_function_names) + sorted_get_jinja2_function_names = sorted(list(set(get_jinja2_function_names))) sorted_mapping_function_names = sorted(mapping_function_names) assert sorted_get_jinja2_function_names == sorted_mapping_function_names From 227d2a265b05e41143ed9d4543e8abf913ceca8c Mon Sep 17 00:00:00 2001 From: Ken Celenza Date: Fri, 18 Aug 2023 23:43:22 -0400 Subject: [PATCH 11/21] Pin mkdocstrings-python to 1.4.0 to have compatibility with griffe 0.33.0 (#347) --- docs/requirements.txt | 2 +- poetry.lock | 18 +++++++++--------- pyproject.toml | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 33f654ef..e3d80113 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -6,4 +6,4 @@ mkdocs-python-classy==0.1.3 mkdocs-version-annotations==1.0.0 # Automatic documentation from sources, for MkDocs mkdocstrings==0.22.0 -mkdocstrings-python==1.1.2 \ No newline at end of file +mkdocstrings-python==1.4.0 \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index eede6c71..1a04c840 100644 --- a/poetry.lock +++ b/poetry.lock @@ -605,13 +605,13 @@ gitdb = ">=4.0.1,<5" [[package]] name = "griffe" -version = "0.32.3" +version = "0.33.0" 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-0.32.3-py3-none-any.whl", hash = "sha256:d9471934225818bf8f309822f70451cc6abb4b24e59e0bb27402a45f9412510f"}, - {file = "griffe-0.32.3.tar.gz", hash = "sha256:14983896ad581f59d5ad7b6c9261ff12bdaa905acccc1129341d13e545da8521"}, + {file = "griffe-0.33.0-py3-none-any.whl", hash = "sha256:16af15d0140c0a5f0b2628d33235fef91a6d8a832dd7cff5759dfe6b7d7a7a49"}, + {file = "griffe-0.33.0.tar.gz", hash = "sha256:783bcb7e7f0d346fcb0cb8072667ca8f6ce7ff776bb278fbccf8a3a753a793e4"}, ] [package.dependencies] @@ -1209,17 +1209,17 @@ python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"] [[package]] name = "mkdocstrings-python" -version = "1.1.2" +version = "1.4.0" description = "A Python handler for mkdocstrings." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "mkdocstrings_python-1.1.2-py3-none-any.whl", hash = "sha256:c2b652a850fec8e85034a9cdb3b45f8ad1a558686edc20ed1f40b4e17e62070f"}, - {file = "mkdocstrings_python-1.1.2.tar.gz", hash = "sha256:f28bdcacb9bcdb44b6942a5642c1ea8b36870614d33e29e3c923e204a8d8ed61"}, + {file = "mkdocstrings_python-1.4.0-py3-none-any.whl", hash = "sha256:46f4b0ed8540c6bfd0c3f50471831a7bdb9a1bf35f24400525721d7555aa355c"}, + {file = "mkdocstrings_python-1.4.0.tar.gz", hash = "sha256:c92304c402928a05c793203dadee7a1a51b5ae56404fd594d0b2db49a7b3957a"}, ] [package.dependencies] -griffe = ">=0.24" +griffe = ">=0.33" mkdocstrings = ">=0.20" [[package]] @@ -2465,4 +2465,4 @@ optionals = ["jsonschema", "napalm"] [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "ba0a3c213ba10e4eedf9c4fdc73090f89d1ac86face85422c546c5db5f1816b2" +content-hash = "f738c1bfadb7e1d5cc6d9166b372979c83e4c44aec704c781adb9f3375b61b35" diff --git a/pyproject.toml b/pyproject.toml index 47cd4d71..5bebe5a4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ mypy = "*" mkdocs = "1.4.3" mkdocs-material = "8.5.8" mkdocstrings = "0.22.0" -mkdocstrings-python = "1.1.2" +mkdocstrings-python = "1.4.0" mkdocs-version-annotations = "1.0.0" mkdocs-python-classy = "0.1.3" From 565e2ba91de0218523ef12c58bcef2cfe8878f4b Mon Sep 17 00:00:00 2001 From: Ken Celenza Date: Thu, 24 Aug 2023 10:22:09 -0400 Subject: [PATCH 12/21] Update library mappings to be correct order (#350) --- development_scripts.py | 48 ++++++++++---------- docs/user/lib_mapper/ansible.md | 2 +- docs/user/lib_mapper/ansible_reverse.md | 2 +- docs/user/lib_mapper/napalm.md | 2 +- docs/user/lib_mapper/napalm_reverse.md | 2 +- docs/user/lib_mapper/ntctemplates.md | 2 +- docs/user/lib_mapper/ntctemplates_reverse.md | 2 +- docs/user/lib_mapper/pyats.md | 2 +- docs/user/lib_mapper/pyats_reverse.md | 2 +- docs/user/lib_mapper/pyntc.md | 2 +- docs/user/lib_mapper/pyntc_reverse.md | 2 +- docs/user/lib_mapper/scrapli.md | 2 +- docs/user/lib_mapper/scrapli_reverse.md | 2 +- 13 files changed, 36 insertions(+), 36 deletions(-) diff --git a/development_scripts.py b/development_scripts.py index 326633a0..1ca53dab 100644 --- a/development_scripts.py +++ b/development_scripts.py @@ -35,74 +35,74 @@ MAPPER = { "LIB_MAPPER_TEMPLATE": { "ansible": { - "header_src": "NORMALIZED", - "header_dst": "ANSIBLE", + "header_src": "ANSIBLE", + "header_dst": "NORMALIZED", "_dict": lib_mapper.ANSIBLE_LIB_MAPPER, "_file": "docs/user/lib_mapper/ansible.md", }, "ansible_reverse": { - "header_src": "ANSIBLE", - "header_dst": "NORMALIZED", + "header_src": "NORMALIZED", + "header_dst": "ANSIBLE", "_dict": lib_mapper.ANSIBLE_LIB_MAPPER_REVERSE, "_file": "docs/user/lib_mapper/ansible_reverse.md", }, "napalm": { - "header_src": "NORMALIZED", - "header_dst": "NAPALM", + "header_src": "NAPALM", + "header_dst": "NORMALIZED", "_dict": lib_mapper.NAPALM_LIB_MAPPER, "_file": "docs/user/lib_mapper/napalm.md", }, "napalm_reverse": { - "header_src": "NAPALM", - "header_dst": "NORMALIZED", + "header_src": "NORMALIZED", + "header_dst": "NAPALM", "_dict": lib_mapper.NAPALM_LIB_MAPPER_REVERSE, "_file": "docs/user/lib_mapper/napalm_reverse.md", }, "ntctemplates": { - "header_src": "NORMALIZED", - "header_dst": "NTCTEMPLATES", + "header_src": "NTCTEMPLATES", + "header_dst": "NORMALIZED", "_dict": lib_mapper.NTCTEMPLATES_LIB_MAPPER, "_file": "docs/user/lib_mapper/ntctemplates.md", }, "ntctemplates_reverse": { - "header_src": "NTCTEMPLATES", - "header_dst": "NORMALIZED", + "header_src": "NORMALIZED", + "header_dst": "NTCTEMPLATES", "_dict": lib_mapper.NTCTEMPLATES_LIB_MAPPER_REVERSE, "_file": "docs/user/lib_mapper/ntctemplates_reverse.md", }, "pyats": { - "header_src": "NORMALIZED", - "header_dst": "PYATS", + "header_src": "PYATS", + "header_dst": "NORMALIZED", "_dict": lib_mapper.PYATS_LIB_MAPPER, "_file": "docs/user/lib_mapper/pyats.md", }, "pyats_reverse": { - "header_src": "PYATS", - "header_dst": "NORMALIZED", + "header_src": "NORMALIZED", + "header_dst": "PYATS", "_dict": lib_mapper.PYATS_LIB_MAPPER_REVERSE, "_file": "docs/user/lib_mapper/pyats_reverse.md", }, "pyntc": { - "header_src": "NORMALIZED", - "header_dst": "PYNTC", + "header_src": "PYNTC", + "header_dst": "NORMALIZED", "_dict": lib_mapper.PYNTC_LIB_MAPPER, "_file": "docs/user/lib_mapper/pyntc.md", }, "pyntc_reverse": { - "header_src": "PYNTC", - "header_dst": "NORMALIZED", + "header_src": "NORMALIZED", + "header_dst": "PYNTC", "_dict": lib_mapper.PYNTC_LIB_MAPPER_REVERSE, "_file": "docs/user/lib_mapper/pyntc_reverse.md", }, "scrapli": { - "header_src": "NORMALIZED", - "header_dst": "SCRAPLI", + "header_src": "SCRAPLI", + "header_dst": "NORMALIZED", "_dict": lib_mapper.SCRAPLI_LIB_MAPPER, "_file": "docs/user/lib_mapper/scrapli.md", }, "scrapli_reverse": { - "header_src": "SCRAPLI", - "header_dst": "NORMALIZED", + "header_src": "NORMALIZED", + "header_dst": "SCRAPLI", "_dict": lib_mapper.SCRAPLI_LIB_MAPPER_REVERSE, "_file": "docs/user/lib_mapper/scrapli_reverse.md", }, diff --git a/docs/user/lib_mapper/ansible.md b/docs/user/lib_mapper/ansible.md index 716de08b..0bb17484 100644 --- a/docs/user/lib_mapper/ansible.md +++ b/docs/user/lib_mapper/ansible.md @@ -1,4 +1,4 @@ -| NORMALIZED | | ANSIBLE | +| ANSIBLE | | NORMALIZED | | ---------- | -- | ------ | | arista.eos.eos | → | arista_eos | | arubanetworks.aoscx | → | aruba_aoscx | diff --git a/docs/user/lib_mapper/ansible_reverse.md b/docs/user/lib_mapper/ansible_reverse.md index 907c0dc8..c7c9c873 100644 --- a/docs/user/lib_mapper/ansible_reverse.md +++ b/docs/user/lib_mapper/ansible_reverse.md @@ -1,4 +1,4 @@ -| ANSIBLE | | NORMALIZED | +| NORMALIZED | | ANSIBLE | | ---------- | -- | ------ | | arista_eos | → | arista.eos.eos | | aruba_aoscx | → | arubanetworks.aoscx | diff --git a/docs/user/lib_mapper/napalm.md b/docs/user/lib_mapper/napalm.md index 4216e4c0..9a352b26 100644 --- a/docs/user/lib_mapper/napalm.md +++ b/docs/user/lib_mapper/napalm.md @@ -1,4 +1,4 @@ -| NORMALIZED | | NAPALM | +| NAPALM | | NORMALIZED | | ---------- | -- | ------ | | aoscx | → | aruba_aoscx | | asa | → | cisco_asa | diff --git a/docs/user/lib_mapper/napalm_reverse.md b/docs/user/lib_mapper/napalm_reverse.md index a4b7b8b3..1a2bad98 100644 --- a/docs/user/lib_mapper/napalm_reverse.md +++ b/docs/user/lib_mapper/napalm_reverse.md @@ -1,4 +1,4 @@ -| NAPALM | | NORMALIZED | +| NORMALIZED | | NAPALM | | ---------- | -- | ------ | | arista_eos | → | eos | | aruba_aoscx | → | aoscx | diff --git a/docs/user/lib_mapper/ntctemplates.md b/docs/user/lib_mapper/ntctemplates.md index e5d2ff86..61524043 100644 --- a/docs/user/lib_mapper/ntctemplates.md +++ b/docs/user/lib_mapper/ntctemplates.md @@ -1,4 +1,4 @@ -| NORMALIZED | | NTCTEMPLATES | +| NTCTEMPLATES | | NORMALIZED | | ---------- | -- | ------ | | a10 | → | a10 | | accedian | → | accedian | diff --git a/docs/user/lib_mapper/ntctemplates_reverse.md b/docs/user/lib_mapper/ntctemplates_reverse.md index 61524043..e5d2ff86 100644 --- a/docs/user/lib_mapper/ntctemplates_reverse.md +++ b/docs/user/lib_mapper/ntctemplates_reverse.md @@ -1,4 +1,4 @@ -| NTCTEMPLATES | | NORMALIZED | +| NORMALIZED | | NTCTEMPLATES | | ---------- | -- | ------ | | a10 | → | a10 | | accedian | → | accedian | diff --git a/docs/user/lib_mapper/pyats.md b/docs/user/lib_mapper/pyats.md index b6e7886f..1ee1d38c 100644 --- a/docs/user/lib_mapper/pyats.md +++ b/docs/user/lib_mapper/pyats.md @@ -1,4 +1,4 @@ -| NORMALIZED | | PYATS | +| PYATS | | NORMALIZED | | ---------- | -- | ------ | | asa | → | cisco_asa | | bigip | → | f5_tmsh | diff --git a/docs/user/lib_mapper/pyats_reverse.md b/docs/user/lib_mapper/pyats_reverse.md index 61acbd08..d26bd04f 100644 --- a/docs/user/lib_mapper/pyats_reverse.md +++ b/docs/user/lib_mapper/pyats_reverse.md @@ -1,4 +1,4 @@ -| PYATS | | NORMALIZED | +| NORMALIZED | | PYATS | | ---------- | -- | ------ | | cisco_asa | → | asa | | cisco_dnac | → | dnac | diff --git a/docs/user/lib_mapper/pyntc.md b/docs/user/lib_mapper/pyntc.md index 5ff18d52..e6bdde4f 100644 --- a/docs/user/lib_mapper/pyntc.md +++ b/docs/user/lib_mapper/pyntc.md @@ -1,4 +1,4 @@ -| NORMALIZED | | PYNTC | +| PYNTC | | NORMALIZED | | ---------- | -- | ------ | | arista_eos_eapi | → | arista_eos | | cisco_aireos_ssh | → | cisco_wlc | diff --git a/docs/user/lib_mapper/pyntc_reverse.md b/docs/user/lib_mapper/pyntc_reverse.md index 46495200..9615063b 100644 --- a/docs/user/lib_mapper/pyntc_reverse.md +++ b/docs/user/lib_mapper/pyntc_reverse.md @@ -1,4 +1,4 @@ -| PYNTC | | NORMALIZED | +| NORMALIZED | | PYNTC | | ---------- | -- | ------ | | arista_eos | → | arista_eos_eapi | | cisco_asa | → | cisco_asa_ssh | diff --git a/docs/user/lib_mapper/scrapli.md b/docs/user/lib_mapper/scrapli.md index bf296069..d08f35a7 100644 --- a/docs/user/lib_mapper/scrapli.md +++ b/docs/user/lib_mapper/scrapli.md @@ -1,4 +1,4 @@ -| NORMALIZED | | SCRAPLI | +| SCRAPLI | | NORMALIZED | | ---------- | -- | ------ | | arista_eos | → | arista_eos | | aruba_aoscx | → | aruba_aoscx | diff --git a/docs/user/lib_mapper/scrapli_reverse.md b/docs/user/lib_mapper/scrapli_reverse.md index 8cfa8840..ffb981a7 100644 --- a/docs/user/lib_mapper/scrapli_reverse.md +++ b/docs/user/lib_mapper/scrapli_reverse.md @@ -1,4 +1,4 @@ -| SCRAPLI | | NORMALIZED | +| NORMALIZED | | SCRAPLI | | ---------- | -- | ------ | | arista_eos | → | arista_eos | | aruba_aoscx | → | aruba_aoscx | From 1bed7bcebafcaffe21135defe7fae894493befa3 Mon Sep 17 00:00:00 2001 From: mzb Date: Tue, 29 Aug 2023 04:54:39 +0200 Subject: [PATCH 13/21] Fix for ASN functions (#339) * Fixing int to asdot notation * Fixing int to asdot notation * Documentation updates for int_to_asdot * Rever table formatting in include_jinja_list.md --- docs/user/include_jinja_list.md | 2 +- netutils/asn.py | 12 ++++++------ netutils/utils.py | 2 +- tests/unit/test_asn.py | 16 ++++++++-------- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/user/include_jinja_list.md b/docs/user/include_jinja_list.md index 78191a50..156ee645 100644 --- a/docs/user/include_jinja_list.md +++ b/docs/user/include_jinja_list.md @@ -1,7 +1,7 @@ | Filter name | Function | | ---------- | ------ | | asn_to_int | netutils.asn.asn_to_int | -| int_to_asn | netutils.asn.int_to_asn | +| int_to_asdot | netutils.asn.int_to_asdot | | bits_to_name | netutils.bandwidth.bits_to_name | | bytes_to_name | netutils.bandwidth.bytes_to_name | | name_to_bits | netutils.bandwidth.name_to_bits | diff --git a/netutils/asn.py b/netutils/asn.py index f3833908..458dbdd3 100644 --- a/netutils/asn.py +++ b/netutils/asn.py @@ -26,20 +26,20 @@ def asn_to_int(asplain: str) -> int: return int(asplain) -def int_to_asn(asn_int: int) -> str: - """Convert integer to standardized asplain notation for BGP ASN. +def int_to_asdot(asn_int: int) -> str: + """Convert integer to standardized asdot notation for BGP ASN. Args: asn_int: Integer value of the BGP ASN. Returns: - `asplain` notated BGP ASN as a string. + `asdot` notated BGP ASN as a string. Examples: - >>> from netutils.asn import int_to_asn - >>> int_to_asn(65000) + >>> from netutils.asn import int_to_asdot + >>> int_to_asdot(65000) '65000' - >>> int_to_asn(4259840111) + >>> int_to_asdot(4259840111) '65000.111' >>> """ diff --git a/netutils/utils.py b/netutils/utils.py index 5d60c72e..3c011314 100644 --- a/netutils/utils.py +++ b/netutils/utils.py @@ -5,7 +5,7 @@ _JINJA2_FUNCTION_MAPPINGS = { "asn_to_int": "asn.asn_to_int", - "int_to_asn": "asn.int_to_asn", + "int_to_asdot": "asn.int_to_asdot", "name_to_bits": "bandwidth.name_to_bits", "name_to_bytes": "bandwidth.name_to_bytes", "bits_to_name": "bandwidth.bits_to_name", diff --git a/tests/unit/test_asn.py b/tests/unit/test_asn.py index b73a8aa8..2fda4741 100644 --- a/tests/unit/test_asn.py +++ b/tests/unit/test_asn.py @@ -12,7 +12,7 @@ {"sent": {"asplain": "0.65526"}, "received": 65526}, ] -int_to_asn = [ +int_to_asdot = [ {"sent": {"asn_int": 6500}, "received": "6500"}, {"sent": {"asn_int": 425985111}, "received": "6500.1111"}, {"sent": {"asn_int": 425984001}, "received": "6500.1"}, @@ -25,7 +25,7 @@ {"sent": {"asn_int": 4294967295}, "received": "65535.65535"}, ] -int_to_asn_exceptions = [ +int_to_asdot_exceptions = [ {"asn_int": "one22"}, {"asn_int": "not_an_int"}, {"asn_int": "4294967296"}, @@ -39,12 +39,12 @@ def test_asn_to_int(data): assert asn.asn_to_int(**data["sent"]) == data["received"] -@pytest.mark.parametrize("data", int_to_asn) -def test_int_to_asn(data): - assert asn.int_to_asn(**data["sent"]) == data["received"] +@pytest.mark.parametrize("data", int_to_asdot) +def test_int_to_asdot(data): + assert asn.int_to_asdot(**data["sent"]) == data["received"] -@pytest.mark.parametrize("data", int_to_asn_exceptions) -def test_int_to_asn_exceptions(data): +@pytest.mark.parametrize("data", int_to_asdot_exceptions) +def test_int_to_asdot_exceptions(data): with pytest.raises(ValueError): - assert asn.int_to_asn(**data) + assert asn.int_to_asdot(**data) From aa2b9aaf61b683079925af5303d41da638c27919 Mon Sep 17 00:00:00 2001 From: Ken Celenza Date: Tue, 29 Aug 2023 13:39:52 -0400 Subject: [PATCH 14/21] Update docs dependencies (#356) --- docs/dev/dev_environment.md | 2 +- docs/requirements.txt | 6 +- mkdocs.yml | 7 +- poetry.lock | 983 ++++++++++++++++++------------------ pyproject.toml | 13 +- tests/unit/test_basics.py | 5 +- 6 files changed, 519 insertions(+), 497 deletions(-) diff --git a/docs/dev/dev_environment.md b/docs/dev/dev_environment.md index 9fabda72..ea607075 100644 --- a/docs/dev/dev_environment.md +++ b/docs/dev/dev_environment.md @@ -51,7 +51,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. diff --git a/docs/requirements.txt b/docs/requirements.txt index e3d80113..ff233eef 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,9 +1,9 @@ -mkdocs==1.4.3 +mkdocs==1.5.2 # Material for MkDocs theme -mkdocs-material==8.5.8 +mkdocs-material==9.2.4 mkdocs-python-classy==0.1.3 # Render custom markdown for version added/changed/remove notes mkdocs-version-annotations==1.0.0 # Automatic documentation from sources, for MkDocs mkdocstrings==0.22.0 -mkdocstrings-python==1.4.0 \ No newline at end of file +mkdocstrings-python==1.6.0 \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 21297829..e4c71716 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -14,13 +14,14 @@ theme: - "python" - "yaml" features: - - "navigation.tracking" + - "content.code.copy" + - "navigation.indexes" - "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: diff --git a/poetry.lock b/poetry.lock index 1a04c840..e42be1e0 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,15 +1,4 @@ -# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. - -[[package]] -name = "alabaster" -version = "0.7.13" -description = "A configurable sidebar-enabled Sphinx theme" -optional = false -python-versions = ">=3.6" -files = [ - {file = "alabaster-0.7.13-py3-none-any.whl", hash = "sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3"}, - {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"}, -] +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "astroid" @@ -134,6 +123,24 @@ files = [ tests = ["pytest (>=3.2.1,!=3.3.0)"] typecheck = ["mypy"] +[[package]] +name = "beautifulsoup4" +version = "4.12.2" +description = "Screen-scraping library" +optional = false +python-versions = ">=3.6.0" +files = [ + {file = "beautifulsoup4-4.12.2-py3-none-any.whl", hash = "sha256:bd2520ca0d9d7d12694a53d44ac482d181b4ec1888909b035a3dbf40d0f57d4a"}, + {file = "beautifulsoup4-4.12.2.tar.gz", hash = "sha256:492bbc69dca35d12daac71c4db1bfff0c876c00ef4a2ffacce226d4638eb72da"}, +] + +[package.dependencies] +soupsieve = ">1.2" + +[package.extras] +html5lib = ["html5lib"] +lxml = ["lxml"] + [[package]] name = "black" version = "23.7.0" @@ -353,13 +360,13 @@ files = [ [[package]] name = "click" -version = "8.1.6" +version = "8.1.7" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" files = [ - {file = "click-8.1.6-py3-none-any.whl", hash = "sha256:fa244bb30b3b5ee2cae3da8f55c9e5e0c0e86093306301fb418eb9dc40fbded5"}, - {file = "click-8.1.6.tar.gz", hash = "sha256:48ee849951919527a045bfe3bf7baa8a959c423134e1a5b98c05c20ba75a1cbd"}, + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, ] [package.dependencies] @@ -378,71 +385,63 @@ files = [ [[package]] name = "coverage" -version = "7.2.7" +version = "7.3.0" description = "Code coverage measurement for Python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "coverage-7.2.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8"}, - {file = "coverage-7.2.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb"}, - {file = "coverage-7.2.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6"}, - {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2"}, - {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063"}, - {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1"}, - {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353"}, - {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495"}, - {file = "coverage-7.2.7-cp310-cp310-win32.whl", hash = "sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818"}, - {file = "coverage-7.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850"}, - {file = "coverage-7.2.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f"}, - {file = "coverage-7.2.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe"}, - {file = "coverage-7.2.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3"}, - {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f"}, - {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb"}, - {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833"}, - {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97"}, - {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a"}, - {file = "coverage-7.2.7-cp311-cp311-win32.whl", hash = "sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a"}, - {file = "coverage-7.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562"}, - {file = "coverage-7.2.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4"}, - {file = "coverage-7.2.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4"}, - {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01"}, - {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6"}, - {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d"}, - {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de"}, - {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d"}, - {file = "coverage-7.2.7-cp312-cp312-win32.whl", hash = "sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511"}, - {file = "coverage-7.2.7-cp312-cp312-win_amd64.whl", hash = "sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3"}, - {file = "coverage-7.2.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:58c2ccc2f00ecb51253cbe5d8d7122a34590fac9646a960d1430d5b15321d95f"}, - {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d22656368f0e6189e24722214ed8d66b8022db19d182927b9a248a2a8a2f67eb"}, - {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a895fcc7b15c3fc72beb43cdcbdf0ddb7d2ebc959edac9cef390b0d14f39f8a9"}, - {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e84606b74eb7de6ff581a7915e2dab7a28a0517fbe1c9239eb227e1354064dcd"}, - {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0a5f9e1dbd7fbe30196578ca36f3fba75376fb99888c395c5880b355e2875f8a"}, - {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:419bfd2caae268623dd469eff96d510a920c90928b60f2073d79f8fe2bbc5959"}, - {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2aee274c46590717f38ae5e4650988d1af340fe06167546cc32fe2f58ed05b02"}, - {file = "coverage-7.2.7-cp37-cp37m-win32.whl", hash = "sha256:61b9a528fb348373c433e8966535074b802c7a5d7f23c4f421e6c6e2f1697a6f"}, - {file = "coverage-7.2.7-cp37-cp37m-win_amd64.whl", hash = "sha256:b1c546aca0ca4d028901d825015dc8e4d56aac4b541877690eb76490f1dc8ed0"}, - {file = "coverage-7.2.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5"}, - {file = "coverage-7.2.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5"}, - {file = "coverage-7.2.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9"}, - {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6"}, - {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e"}, - {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050"}, - {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5"}, - {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f"}, - {file = "coverage-7.2.7-cp38-cp38-win32.whl", hash = "sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e"}, - {file = "coverage-7.2.7-cp38-cp38-win_amd64.whl", hash = "sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c"}, - {file = "coverage-7.2.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9"}, - {file = "coverage-7.2.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2"}, - {file = "coverage-7.2.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7"}, - {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e"}, - {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1"}, - {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9"}, - {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250"}, - {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2"}, - {file = "coverage-7.2.7-cp39-cp39-win32.whl", hash = "sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb"}, - {file = "coverage-7.2.7-cp39-cp39-win_amd64.whl", hash = "sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27"}, - {file = "coverage-7.2.7-pp37.pp38.pp39-none-any.whl", hash = "sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d"}, - {file = "coverage-7.2.7.tar.gz", hash = "sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59"}, + {file = "coverage-7.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:db76a1bcb51f02b2007adacbed4c88b6dee75342c37b05d1822815eed19edee5"}, + {file = "coverage-7.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c02cfa6c36144ab334d556989406837336c1d05215a9bdf44c0bc1d1ac1cb637"}, + {file = "coverage-7.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:477c9430ad5d1b80b07f3c12f7120eef40bfbf849e9e7859e53b9c93b922d2af"}, + {file = "coverage-7.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce2ee86ca75f9f96072295c5ebb4ef2a43cecf2870b0ca5e7a1cbdd929cf67e1"}, + {file = "coverage-7.3.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68d8a0426b49c053013e631c0cdc09b952d857efa8f68121746b339912d27a12"}, + {file = "coverage-7.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b3eb0c93e2ea6445b2173da48cb548364f8f65bf68f3d090404080d338e3a689"}, + {file = "coverage-7.3.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:90b6e2f0f66750c5a1178ffa9370dec6c508a8ca5265c42fbad3ccac210a7977"}, + {file = "coverage-7.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:96d7d761aea65b291a98c84e1250cd57b5b51726821a6f2f8df65db89363be51"}, + {file = "coverage-7.3.0-cp310-cp310-win32.whl", hash = "sha256:63c5b8ecbc3b3d5eb3a9d873dec60afc0cd5ff9d9f1c75981d8c31cfe4df8527"}, + {file = "coverage-7.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:97c44f4ee13bce914272589b6b41165bbb650e48fdb7bd5493a38bde8de730a1"}, + {file = "coverage-7.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:74c160285f2dfe0acf0f72d425f3e970b21b6de04157fc65adc9fd07ee44177f"}, + {file = "coverage-7.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b543302a3707245d454fc49b8ecd2c2d5982b50eb63f3535244fd79a4be0c99d"}, + {file = "coverage-7.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad0f87826c4ebd3ef484502e79b39614e9c03a5d1510cfb623f4a4a051edc6fd"}, + {file = "coverage-7.3.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:13c6cbbd5f31211d8fdb477f0f7b03438591bdd077054076eec362cf2207b4a7"}, + {file = "coverage-7.3.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fac440c43e9b479d1241fe9d768645e7ccec3fb65dc3a5f6e90675e75c3f3e3a"}, + {file = "coverage-7.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:3c9834d5e3df9d2aba0275c9f67989c590e05732439b3318fa37a725dff51e74"}, + {file = "coverage-7.3.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4c8e31cf29b60859876474034a83f59a14381af50cbe8a9dbaadbf70adc4b214"}, + {file = "coverage-7.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7a9baf8e230f9621f8e1d00c580394a0aa328fdac0df2b3f8384387c44083c0f"}, + {file = "coverage-7.3.0-cp311-cp311-win32.whl", hash = "sha256:ccc51713b5581e12f93ccb9c5e39e8b5d4b16776d584c0f5e9e4e63381356482"}, + {file = "coverage-7.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:887665f00ea4e488501ba755a0e3c2cfd6278e846ada3185f42d391ef95e7e70"}, + {file = "coverage-7.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d000a739f9feed900381605a12a61f7aaced6beae832719ae0d15058a1e81c1b"}, + {file = "coverage-7.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:59777652e245bb1e300e620ce2bef0d341945842e4eb888c23a7f1d9e143c446"}, + {file = "coverage-7.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9737bc49a9255d78da085fa04f628a310c2332b187cd49b958b0e494c125071"}, + {file = "coverage-7.3.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5247bab12f84a1d608213b96b8af0cbb30d090d705b6663ad794c2f2a5e5b9fe"}, + {file = "coverage-7.3.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2ac9a1de294773b9fa77447ab7e529cf4fe3910f6a0832816e5f3d538cfea9a"}, + {file = "coverage-7.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:85b7335c22455ec12444cec0d600533a238d6439d8d709d545158c1208483873"}, + {file = "coverage-7.3.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:36ce5d43a072a036f287029a55b5c6a0e9bd73db58961a273b6dc11a2c6eb9c2"}, + {file = "coverage-7.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:211a4576e984f96d9fce61766ffaed0115d5dab1419e4f63d6992b480c2bd60b"}, + {file = "coverage-7.3.0-cp312-cp312-win32.whl", hash = "sha256:56afbf41fa4a7b27f6635bc4289050ac3ab7951b8a821bca46f5b024500e6321"}, + {file = "coverage-7.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:7f297e0c1ae55300ff688568b04ff26b01c13dfbf4c9d2b7d0cb688ac60df479"}, + {file = "coverage-7.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ac0dec90e7de0087d3d95fa0533e1d2d722dcc008bc7b60e1143402a04c117c1"}, + {file = "coverage-7.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:438856d3f8f1e27f8e79b5410ae56650732a0dcfa94e756df88c7e2d24851fcd"}, + {file = "coverage-7.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1084393c6bda8875c05e04fce5cfe1301a425f758eb012f010eab586f1f3905e"}, + {file = "coverage-7.3.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49ab200acf891e3dde19e5aa4b0f35d12d8b4bd805dc0be8792270c71bd56c54"}, + {file = "coverage-7.3.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a67e6bbe756ed458646e1ef2b0778591ed4d1fcd4b146fc3ba2feb1a7afd4254"}, + {file = "coverage-7.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8f39c49faf5344af36042b293ce05c0d9004270d811c7080610b3e713251c9b0"}, + {file = "coverage-7.3.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7df91fb24c2edaabec4e0eee512ff3bc6ec20eb8dccac2e77001c1fe516c0c84"}, + {file = "coverage-7.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:34f9f0763d5fa3035a315b69b428fe9c34d4fc2f615262d6be3d3bf3882fb985"}, + {file = "coverage-7.3.0-cp38-cp38-win32.whl", hash = "sha256:bac329371d4c0d456e8d5f38a9b0816b446581b5f278474e416ea0c68c47dcd9"}, + {file = "coverage-7.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:b859128a093f135b556b4765658d5d2e758e1fae3e7cc2f8c10f26fe7005e543"}, + {file = "coverage-7.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fc0ed8d310afe013db1eedd37176d0839dc66c96bcfcce8f6607a73ffea2d6ba"}, + {file = "coverage-7.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61260ec93f99f2c2d93d264b564ba912bec502f679793c56f678ba5251f0393"}, + {file = "coverage-7.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97af9554a799bd7c58c0179cc8dbf14aa7ab50e1fd5fa73f90b9b7215874ba28"}, + {file = "coverage-7.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3558e5b574d62f9c46b76120a5c7c16c4612dc2644c3d48a9f4064a705eaee95"}, + {file = "coverage-7.3.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37d5576d35fcb765fca05654f66aa71e2808d4237d026e64ac8b397ffa66a56a"}, + {file = "coverage-7.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:07ea61bcb179f8f05ffd804d2732b09d23a1238642bf7e51dad62082b5019b34"}, + {file = "coverage-7.3.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:80501d1b2270d7e8daf1b64b895745c3e234289e00d5f0e30923e706f110334e"}, + {file = "coverage-7.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4eddd3153d02204f22aef0825409091a91bf2a20bce06fe0f638f5c19a85de54"}, + {file = "coverage-7.3.0-cp39-cp39-win32.whl", hash = "sha256:2d22172f938455c156e9af2612650f26cceea47dc86ca048fa4e0b2d21646ad3"}, + {file = "coverage-7.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:60f64e2007c9144375dd0f480a54d6070f00bb1a28f65c408370544091c9bc9e"}, + {file = "coverage-7.3.0-pp38.pp39.pp310-none-any.whl", hash = "sha256:5492a6ce3bdb15c6ad66cb68a0244854d9917478877a25671d70378bdc8562d0"}, + {file = "coverage-7.3.0.tar.gz", hash = "sha256:49dbb19cdcafc130f597d9e04a29d0a032ceedf729e41b181f51cd170e6ee865"}, ] [package.extras] @@ -450,34 +449,34 @@ toml = ["tomli"] [[package]] name = "cryptography" -version = "41.0.2" +version = "41.0.3" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = true python-versions = ">=3.7" files = [ - {file = "cryptography-41.0.2-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:01f1d9e537f9a15b037d5d9ee442b8c22e3ae11ce65ea1f3316a41c78756b711"}, - {file = "cryptography-41.0.2-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:079347de771f9282fbfe0e0236c716686950c19dee1b76240ab09ce1624d76d7"}, - {file = "cryptography-41.0.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:439c3cc4c0d42fa999b83ded80a9a1fb54d53c58d6e59234cfe97f241e6c781d"}, - {file = "cryptography-41.0.2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f14ad275364c8b4e525d018f6716537ae7b6d369c094805cae45300847e0894f"}, - {file = "cryptography-41.0.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:84609ade00a6ec59a89729e87a503c6e36af98ddcd566d5f3be52e29ba993182"}, - {file = "cryptography-41.0.2-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:49c3222bb8f8e800aead2e376cbef687bc9e3cb9b58b29a261210456a7783d83"}, - {file = "cryptography-41.0.2-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:d73f419a56d74fef257955f51b18d046f3506270a5fd2ac5febbfa259d6c0fa5"}, - {file = "cryptography-41.0.2-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:2a034bf7d9ca894720f2ec1d8b7b5832d7e363571828037f9e0c4f18c1b58a58"}, - {file = "cryptography-41.0.2-cp37-abi3-win32.whl", hash = "sha256:d124682c7a23c9764e54ca9ab5b308b14b18eba02722b8659fb238546de83a76"}, - {file = "cryptography-41.0.2-cp37-abi3-win_amd64.whl", hash = "sha256:9c3fe6534d59d071ee82081ca3d71eed3210f76ebd0361798c74abc2bcf347d4"}, - {file = "cryptography-41.0.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a719399b99377b218dac6cf547b6ec54e6ef20207b6165126a280b0ce97e0d2a"}, - {file = "cryptography-41.0.2-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:182be4171f9332b6741ee818ec27daff9fb00349f706629f5cbf417bd50e66fd"}, - {file = "cryptography-41.0.2-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:7a9a3bced53b7f09da251685224d6a260c3cb291768f54954e28f03ef14e3766"}, - {file = "cryptography-41.0.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f0dc40e6f7aa37af01aba07277d3d64d5a03dc66d682097541ec4da03cc140ee"}, - {file = "cryptography-41.0.2-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:674b669d5daa64206c38e507808aae49904c988fa0a71c935e7006a3e1e83831"}, - {file = "cryptography-41.0.2-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7af244b012711a26196450d34f483357e42aeddb04128885d95a69bd8b14b69b"}, - {file = "cryptography-41.0.2-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9b6d717393dbae53d4e52684ef4f022444fc1cce3c48c38cb74fca29e1f08eaa"}, - {file = "cryptography-41.0.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:192255f539d7a89f2102d07d7375b1e0a81f7478925b3bc2e0549ebf739dae0e"}, - {file = "cryptography-41.0.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f772610fe364372de33d76edcd313636a25684edb94cee53fd790195f5989d14"}, - {file = "cryptography-41.0.2-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:b332cba64d99a70c1e0836902720887fb4529ea49ea7f5462cf6640e095e11d2"}, - {file = "cryptography-41.0.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9a6673c1828db6270b76b22cc696f40cde9043eb90373da5c2f8f2158957f42f"}, - {file = "cryptography-41.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:342f3767e25876751e14f8459ad85e77e660537ca0a066e10e75df9c9e9099f0"}, - {file = "cryptography-41.0.2.tar.gz", hash = "sha256:7d230bf856164de164ecb615ccc14c7fc6de6906ddd5b491f3af90d3514c925c"}, + {file = "cryptography-41.0.3-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:652627a055cb52a84f8c448185922241dd5217443ca194d5739b44612c5e6507"}, + {file = "cryptography-41.0.3-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:8f09daa483aedea50d249ef98ed500569841d6498aa9c9f4b0531b9964658922"}, + {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fd871184321100fb400d759ad0cddddf284c4b696568204d281c902fc7b0d81"}, + {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84537453d57f55a50a5b6835622ee405816999a7113267739a1b4581f83535bd"}, + {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:3fb248989b6363906827284cd20cca63bb1a757e0a2864d4c1682a985e3dca47"}, + {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:42cb413e01a5d36da9929baa9d70ca90d90b969269e5a12d39c1e0d475010116"}, + {file = "cryptography-41.0.3-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:aeb57c421b34af8f9fe830e1955bf493a86a7996cc1338fe41b30047d16e962c"}, + {file = "cryptography-41.0.3-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6af1c6387c531cd364b72c28daa29232162010d952ceb7e5ca8e2827526aceae"}, + {file = "cryptography-41.0.3-cp37-abi3-win32.whl", hash = "sha256:0d09fb5356f975974dbcb595ad2d178305e5050656affb7890a1583f5e02a306"}, + {file = "cryptography-41.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:a983e441a00a9d57a4d7c91b3116a37ae602907a7618b882c8013b5762e80574"}, + {file = "cryptography-41.0.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5259cb659aa43005eb55a0e4ff2c825ca111a0da1814202c64d28a985d33b087"}, + {file = "cryptography-41.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:67e120e9a577c64fe1f611e53b30b3e69744e5910ff3b6e97e935aeb96005858"}, + {file = "cryptography-41.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:7efe8041897fe7a50863e51b77789b657a133c75c3b094e51b5e4b5cec7bf906"}, + {file = "cryptography-41.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ce785cf81a7bdade534297ef9e490ddff800d956625020ab2ec2780a556c313e"}, + {file = "cryptography-41.0.3-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:57a51b89f954f216a81c9d057bf1a24e2f36e764a1ca9a501a6964eb4a6800dd"}, + {file = "cryptography-41.0.3-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:4c2f0d35703d61002a2bbdcf15548ebb701cfdd83cdc12471d2bae80878a4207"}, + {file = "cryptography-41.0.3-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:23c2d778cf829f7d0ae180600b17e9fceea3c2ef8b31a99e3c694cbbf3a24b84"}, + {file = "cryptography-41.0.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:95dd7f261bb76948b52a5330ba5202b91a26fbac13ad0e9fc8a3ac04752058c7"}, + {file = "cryptography-41.0.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:41d7aa7cdfded09b3d73a47f429c298e80796c8e825ddfadc84c8a7f12df212d"}, + {file = "cryptography-41.0.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d0d651aa754ef58d75cec6edfbd21259d93810b73f6ec246436a21b7841908de"}, + {file = "cryptography-41.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:ab8de0d091acbf778f74286f4989cf3d1528336af1b59f3e5d2ebca8b5fe49e1"}, + {file = "cryptography-41.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a74fbcdb2a0d46fe00504f571a2a540532f4c188e6ccf26f1f178480117b33c4"}, + {file = "cryptography-41.0.3.tar.gz", hash = "sha256:6d192741113ef5e30d89dcb5b956ef4e1578f304708701b8b73d38e3e1461f34"}, ] [package.dependencies] @@ -494,39 +493,39 @@ test = ["pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest test-randomorder = ["pytest-randomly"] [[package]] -name = "dill" -version = "0.3.6" -description = "serialize all of python" +name = "cssselect" +version = "1.2.0" +description = "cssselect parses CSS3 Selectors and translates them to XPath 1.0" optional = false python-versions = ">=3.7" files = [ - {file = "dill-0.3.6-py3-none-any.whl", hash = "sha256:a07ffd2351b8c678dfc4a856a3005f8067aea51d6ba6c700796a4d9e280f39f0"}, - {file = "dill-0.3.6.tar.gz", hash = "sha256:e5db55f3687856d8fbdab002ed78544e1c4559a130302693d839dfe8f93f2373"}, + {file = "cssselect-1.2.0-py2.py3-none-any.whl", hash = "sha256:da1885f0c10b60c03ed5eccbb6b68d6eff248d91976fcde348f395d54c9fd35e"}, + {file = "cssselect-1.2.0.tar.gz", hash = "sha256:666b19839cfaddb9ce9d36bfe4c969132c647b92fc9088c4e23f786b30f1b3dc"}, ] -[package.extras] -graph = ["objgraph (>=1.7.2)"] - [[package]] -name = "docutils" -version = "0.18.1" -description = "Docutils -- Python Documentation Utilities" +name = "dill" +version = "0.3.7" +description = "serialize all of Python" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.7" files = [ - {file = "docutils-0.18.1-py2.py3-none-any.whl", hash = "sha256:23010f129180089fbcd3bc08cfefccb3b890b0050e1ca00c867036e9d161b98c"}, - {file = "docutils-0.18.1.tar.gz", hash = "sha256:679987caf361a7539d76e584cbeddc311e3aee937877c87346f31debc63e9d06"}, + {file = "dill-0.3.7-py3-none-any.whl", hash = "sha256:76b122c08ef4ce2eedcd4d1abd8e641114bfc6c2867f49f3c41facf65bf19f5e"}, + {file = "dill-0.3.7.tar.gz", hash = "sha256:cc1c8b182eb3013e24bd475ff2e9295af86c1a38eb1aff128dac8962a9ce3c03"}, ] +[package.extras] +graph = ["objgraph (>=1.7.2)"] + [[package]] name = "exceptiongroup" -version = "1.1.2" +version = "1.1.3" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.1.2-py3-none-any.whl", hash = "sha256:e346e69d186172ca7cf029c8c1d16235aa0e04035e5750b4b95039e65204328f"}, - {file = "exceptiongroup-1.1.2.tar.gz", hash = "sha256:12c3e887d6485d16943a309616de20ae5582633e0a2eda17f4e10fd61c1e8af5"}, + {file = "exceptiongroup-1.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"}, + {file = "exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"}, ] [package.extras] @@ -605,13 +604,13 @@ gitdb = ">=4.0.1,<5" [[package]] name = "griffe" -version = "0.33.0" +version = "0.35.2" 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-0.33.0-py3-none-any.whl", hash = "sha256:16af15d0140c0a5f0b2628d33235fef91a6d8a832dd7cff5759dfe6b7d7a7a49"}, - {file = "griffe-0.33.0.tar.gz", hash = "sha256:783bcb7e7f0d346fcb0cb8072667ca8f6ce7ff776bb278fbccf8a3a753a793e4"}, + {file = "griffe-0.35.2-py3-none-any.whl", hash = "sha256:9650d6d0369c22f29f2c1bec9548ddc7f448f8ca38698a5799f92f736824e749"}, + {file = "griffe-0.35.2.tar.gz", hash = "sha256:84ecfe3df17454993b8dd485201566609ac6706a2eb22e3f402da2a39f9f6b5f"}, ] [package.dependencies] @@ -628,17 +627,6 @@ files = [ {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, ] -[[package]] -name = "imagesize" -version = "1.4.1" -description = "Getting image size from png/jpeg/jpeg2000/gif file" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, - {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, -] - [[package]] name = "importlib-metadata" version = "6.8.0" @@ -844,7 +832,7 @@ files = [ name = "lxml" version = "4.9.3" description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." -optional = true +optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" files = [ {file = "lxml-4.9.3-cp27-cp27m-macosx_11_0_x86_64.whl", hash = "sha256:b0a545b46b526d418eb91754565ba5b63b1c0b12f9bd2f808c852d9b4b2f9b5c"}, @@ -949,19 +937,20 @@ source = ["Cython (>=0.29.35)"] [[package]] name = "markdown" -version = "3.3.7" -description = "Python implementation of Markdown." +version = "3.4.4" +description = "Python implementation of John Gruber's Markdown." optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" 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.4.4-py3-none-any.whl", hash = "sha256:a4c1b65c0957b4bd9e7d86ddc7b3c9868fb9670660f6f99f6d1bca8954d5a941"}, + {file = "Markdown-3.4.4.tar.gz", hash = "sha256:225c6123522495d4119a90b3a3ba31a1e87a70369e03f14799ea9c0d7183a3d6"}, ] [package.dependencies] importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} [package.extras] +docs = ["mdx-gh-links (>=0.2)", "mkdocs (>=1.0)", "mkdocs-nature (>=0.4)"] testing = ["coverage", "pyyaml"] [[package]] @@ -988,6 +977,22 @@ 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 = "markdown2" +version = "2.4.10" +description = "A fast and complete Python implementation of Markdown" +optional = false +python-versions = ">=3.5, <4" +files = [ + {file = "markdown2-2.4.10-py2.py3-none-any.whl", hash = "sha256:e6105800483783831f5dc54f827aa5b44eb137ecef5a70293d8ecfbb4109ecc6"}, + {file = "markdown2-2.4.10.tar.gz", hash = "sha256:cdba126d90dc3aef6f4070ac342f974d63f415678959329cc7909f96cc235d72"}, +] + +[package.extras] +all = ["pygments (>=2.7.3)", "wavedrom"] +code-syntax-highlighting = ["pygments (>=2.7.3)"] +wavedrom = ["wavedrom"] + [[package]] name = "markupsafe" version = "2.1.3" @@ -1082,13 +1087,13 @@ files = [ [[package]] name = "mkdocs" -version = "1.4.3" +version = "1.5.2" description = "Project documentation with Markdown." optional = false python-versions = ">=3.7" files = [ - {file = "mkdocs-1.4.3-py3-none-any.whl", hash = "sha256:6ee46d309bda331aac915cd24aab882c179a933bd9e77b80ce7d2eaaa3f689dd"}, - {file = "mkdocs-1.4.3.tar.gz", hash = "sha256:5955093bbd4dd2e9403c5afaf57324ad8b04f16886512a3ee6ef828956481c57"}, + {file = "mkdocs-1.5.2-py3-none-any.whl", hash = "sha256:60a62538519c2e96fe8426654a67ee177350451616118a41596ae7c876bb7eac"}, + {file = "mkdocs-1.5.2.tar.gz", hash = "sha256:70d0da09c26cff288852471be03c23f0f521fc15cf16ac89c7a3bfb9ae8d24f9"}, ] [package.dependencies] @@ -1097,26 +1102,29 @@ colorama = {version = ">=0.4", markers = "platform_system == \"Windows\""} ghp-import = ">=1.0" importlib-metadata = {version = ">=4.3", markers = "python_version < \"3.10\""} jinja2 = ">=2.11.1" -markdown = ">=3.2.1,<3.4" +markdown = ">=3.2.1" +markupsafe = ">=2.0.1" mergedeep = ">=1.3.4" packaging = ">=20.5" +pathspec = ">=0.11.1" +platformdirs = ">=2.2.0" 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.3)", "jinja2 (==2.11.1)", "markdown (==3.2.1)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "packaging (==20.5)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "typing-extensions (==3.10)", "watchdog (==2.0)"] +min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-import (==1.0)", "importlib-metadata (==4.3)", "jinja2 (==2.11.1)", "markdown (==3.2.1)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "packaging (==20.5)", "pathspec (==0.11.1)", "platformdirs (==2.2.0)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "typing-extensions (==3.10)", "watchdog (==2.0)"] [[package]] name = "mkdocs-autorefs" -version = "0.4.1" +version = "0.5.0" description = "Automatically link across pages in MkDocs." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "mkdocs-autorefs-0.4.1.tar.gz", hash = "sha256:70748a7bd025f9ecd6d6feeba8ba63f8e891a1af55f48e366d6d6e78493aba84"}, - {file = "mkdocs_autorefs-0.4.1-py3-none-any.whl", hash = "sha256:a2248a9501b29dc0cc8ba4c09f4f47ff121945f6ce33d760f145d6f89d313f5b"}, + {file = "mkdocs_autorefs-0.5.0-py3-none-any.whl", hash = "sha256:7930fcb8ac1249f10e683967aeaddc0af49d90702af111a5e390e8b20b3d97ff"}, + {file = "mkdocs_autorefs-0.5.0.tar.gz", hash = "sha256:9a5054a94c08d28855cfab967ada10ed5be76e2bfad642302a610b252c3274c0"}, ] [package.dependencies] @@ -1125,22 +1133,28 @@ mkdocs = ">=1.1" [[package]] name = "mkdocs-material" -version = "8.5.8" +version = "9.2.4" description = "Documentation that simply works" optional = false python-versions = ">=3.7" files = [ - {file = "mkdocs_material-8.5.8-py3-none-any.whl", hash = "sha256:7ff092299e3a63cef99cd87e4a6cc7e7d9ec31fd190d766fd147c35572e6d593"}, - {file = "mkdocs_material-8.5.8.tar.gz", hash = "sha256:61396251819cf7f547f70a09ce6a7edb2ff5d32e47b9199769020b2d20a83d44"}, + {file = "mkdocs_material-9.2.4-py3-none-any.whl", hash = "sha256:2df876367625ff5e0f7112bc19a57521ed21ce9a2b85656baf9bb7f5dc3cb987"}, + {file = "mkdocs_material-9.2.4.tar.gz", hash = "sha256:25008187b89fc376cb4ed2312b1fea4121bf2bd956442f38afdc6b4dcc21c57d"}, ] [package.dependencies] -jinja2 = ">=3.0.2" +babel = ">=2.10.3" +colorama = ">=0.4" +jinja2 = ">=3.0" +lxml = ">=4.6" markdown = ">=3.2" -mkdocs = ">=1.4.0" +mkdocs = ">=1.5.2" mkdocs-material-extensions = ">=1.1" -pygments = ">=2.12" -pymdown-extensions = ">=9.4" +paginate = ">=0.5.6" +pygments = ">=2.14" +pymdown-extensions = ">=9.9.1" +readtime = ">=2.0" +regex = ">=2022.4.24" requests = ">=2.26" [[package]] @@ -1209,52 +1223,53 @@ python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"] [[package]] name = "mkdocstrings-python" -version = "1.4.0" +version = "1.6.0" description = "A Python handler for mkdocstrings." optional = false python-versions = ">=3.8" files = [ - {file = "mkdocstrings_python-1.4.0-py3-none-any.whl", hash = "sha256:46f4b0ed8540c6bfd0c3f50471831a7bdb9a1bf35f24400525721d7555aa355c"}, - {file = "mkdocstrings_python-1.4.0.tar.gz", hash = "sha256:c92304c402928a05c793203dadee7a1a51b5ae56404fd594d0b2db49a7b3957a"}, + {file = "mkdocstrings_python-1.6.0-py3-none-any.whl", hash = "sha256:06f116112b335114372f2554b1bf61b709c74ab72605010e1605c1086932dffe"}, + {file = "mkdocstrings_python-1.6.0.tar.gz", hash = "sha256:6164ccaa6e488abc2a8fbccdfd1f21948c2c344d3f347847783a5d1c6fa2bfbf"}, ] [package.dependencies] -griffe = ">=0.33" +griffe = ">=0.35" mkdocstrings = ">=0.20" [[package]] name = "mypy" -version = "1.4.1" +version = "1.5.1" description = "Optional static typing for Python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "mypy-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:566e72b0cd6598503e48ea610e0052d1b8168e60a46e0bfd34b3acf2d57f96a8"}, - {file = "mypy-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ca637024ca67ab24a7fd6f65d280572c3794665eaf5edcc7e90a866544076878"}, - {file = "mypy-1.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dde1d180cd84f0624c5dcaaa89c89775550a675aff96b5848de78fb11adabcd"}, - {file = "mypy-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8c4d8e89aa7de683e2056a581ce63c46a0c41e31bd2b6d34144e2c80f5ea53dc"}, - {file = "mypy-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:bfdca17c36ae01a21274a3c387a63aa1aafe72bff976522886869ef131b937f1"}, - {file = "mypy-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7549fbf655e5825d787bbc9ecf6028731973f78088fbca3a1f4145c39ef09462"}, - {file = "mypy-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:98324ec3ecf12296e6422939e54763faedbfcc502ea4a4c38502082711867258"}, - {file = "mypy-1.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:141dedfdbfe8a04142881ff30ce6e6653c9685b354876b12e4fe6c78598b45e2"}, - {file = "mypy-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8207b7105829eca6f3d774f64a904190bb2231de91b8b186d21ffd98005f14a7"}, - {file = "mypy-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:16f0db5b641ba159eff72cff08edc3875f2b62b2fa2bc24f68c1e7a4e8232d01"}, - {file = "mypy-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:470c969bb3f9a9efcedbadcd19a74ffb34a25f8e6b0e02dae7c0e71f8372f97b"}, - {file = "mypy-1.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5952d2d18b79f7dc25e62e014fe5a23eb1a3d2bc66318df8988a01b1a037c5b"}, - {file = "mypy-1.4.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:190b6bab0302cec4e9e6767d3eb66085aef2a1cc98fe04936d8a42ed2ba77bb7"}, - {file = "mypy-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9d40652cc4fe33871ad3338581dca3297ff5f2213d0df345bcfbde5162abf0c9"}, - {file = "mypy-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:01fd2e9f85622d981fd9063bfaef1aed6e336eaacca00892cd2d82801ab7c042"}, - {file = "mypy-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2460a58faeea905aeb1b9b36f5065f2dc9a9c6e4c992a6499a2360c6c74ceca3"}, - {file = "mypy-1.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2746d69a8196698146a3dbe29104f9eb6a2a4d8a27878d92169a6c0b74435b6"}, - {file = "mypy-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ae704dcfaa180ff7c4cfbad23e74321a2b774f92ca77fd94ce1049175a21c97f"}, - {file = "mypy-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:43d24f6437925ce50139a310a64b2ab048cb2d3694c84c71c3f2a1626d8101dc"}, - {file = "mypy-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c482e1246726616088532b5e964e39765b6d1520791348e6c9dc3af25b233828"}, - {file = "mypy-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:43b592511672017f5b1a483527fd2684347fdffc041c9ef53428c8dc530f79a3"}, - {file = "mypy-1.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34a9239d5b3502c17f07fd7c0b2ae6b7dd7d7f6af35fbb5072c6208e76295816"}, - {file = "mypy-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5703097c4936bbb9e9bce41478c8d08edd2865e177dc4c52be759f81ee4dd26c"}, - {file = "mypy-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e02d700ec8d9b1859790c0475df4e4092c7bf3272a4fd2c9f33d87fac4427b8f"}, - {file = "mypy-1.4.1-py3-none-any.whl", hash = "sha256:45d32cec14e7b97af848bddd97d85ea4f0db4d5a149ed9676caa4eb2f7402bb4"}, - {file = "mypy-1.4.1.tar.gz", hash = "sha256:9bbcd9ab8ea1f2e1c8031c21445b511442cc45c89951e49bbf852cbb70755b1b"}, + {file = "mypy-1.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f33592ddf9655a4894aef22d134de7393e95fcbdc2d15c1ab65828eee5c66c70"}, + {file = "mypy-1.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:258b22210a4a258ccd077426c7a181d789d1121aca6db73a83f79372f5569ae0"}, + {file = "mypy-1.5.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9ec1f695f0c25986e6f7f8778e5ce61659063268836a38c951200c57479cc12"}, + {file = "mypy-1.5.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:abed92d9c8f08643c7d831300b739562b0a6c9fcb028d211134fc9ab20ccad5d"}, + {file = "mypy-1.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:a156e6390944c265eb56afa67c74c0636f10283429171018446b732f1a05af25"}, + {file = "mypy-1.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6ac9c21bfe7bc9f7f1b6fae441746e6a106e48fc9de530dea29e8cd37a2c0cc4"}, + {file = "mypy-1.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:51cb1323064b1099e177098cb939eab2da42fea5d818d40113957ec954fc85f4"}, + {file = "mypy-1.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:596fae69f2bfcb7305808c75c00f81fe2829b6236eadda536f00610ac5ec2243"}, + {file = "mypy-1.5.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:32cb59609b0534f0bd67faebb6e022fe534bdb0e2ecab4290d683d248be1b275"}, + {file = "mypy-1.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:159aa9acb16086b79bbb0016145034a1a05360626046a929f84579ce1666b315"}, + {file = "mypy-1.5.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f6b0e77db9ff4fda74de7df13f30016a0a663928d669c9f2c057048ba44f09bb"}, + {file = "mypy-1.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:26f71b535dfc158a71264e6dc805a9f8d2e60b67215ca0bfa26e2e1aa4d4d373"}, + {file = "mypy-1.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fc3a600f749b1008cc75e02b6fb3d4db8dbcca2d733030fe7a3b3502902f161"}, + {file = "mypy-1.5.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:26fb32e4d4afa205b24bf645eddfbb36a1e17e995c5c99d6d00edb24b693406a"}, + {file = "mypy-1.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:82cb6193de9bbb3844bab4c7cf80e6227d5225cc7625b068a06d005d861ad5f1"}, + {file = "mypy-1.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4a465ea2ca12804d5b34bb056be3a29dc47aea5973b892d0417c6a10a40b2d65"}, + {file = "mypy-1.5.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9fece120dbb041771a63eb95e4896791386fe287fefb2837258925b8326d6160"}, + {file = "mypy-1.5.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d28ddc3e3dfeab553e743e532fb95b4e6afad51d4706dd22f28e1e5e664828d2"}, + {file = "mypy-1.5.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:57b10c56016adce71fba6bc6e9fd45d8083f74361f629390c556738565af8eeb"}, + {file = "mypy-1.5.1-cp38-cp38-win_amd64.whl", hash = "sha256:ff0cedc84184115202475bbb46dd99f8dcb87fe24d5d0ddfc0fe6b8575c88d2f"}, + {file = "mypy-1.5.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8f772942d372c8cbac575be99f9cc9d9fb3bd95c8bc2de6c01411e2c84ebca8a"}, + {file = "mypy-1.5.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5d627124700b92b6bbaa99f27cbe615c8ea7b3402960f6372ea7d65faf376c14"}, + {file = "mypy-1.5.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:361da43c4f5a96173220eb53340ace68cda81845cd88218f8862dfb0adc8cddb"}, + {file = "mypy-1.5.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:330857f9507c24de5c5724235e66858f8364a0693894342485e543f5b07c8693"}, + {file = "mypy-1.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:c543214ffdd422623e9fedd0869166c2f16affe4ba37463975043ef7d2ea8770"}, + {file = "mypy-1.5.1-py3-none-any.whl", hash = "sha256:f757063a83970d67c444f6e01d9550a7402322af3557ce7630d3c957386fa8f5"}, + {file = "mypy-1.5.1.tar.gz", hash = "sha256:b031b9601f1060bf1281feab89697324726ba0c0bae9d7cd7ab4b690940f0b92"}, ] [package.dependencies] @@ -1265,7 +1280,6 @@ typing-extensions = ">=4.1.0" [package.extras] dmypy = ["psutil (>=4.0)"] install-types = ["pip"] -python2 = ["typed-ast (>=1.4.0,<2)"] reports = ["lxml"] [[package]] @@ -1382,15 +1396,25 @@ files = [ {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, ] +[[package]] +name = "paginate" +version = "0.5.6" +description = "Divides large result sets into pages for easier browsing" +optional = false +python-versions = "*" +files = [ + {file = "paginate-0.5.6.tar.gz", hash = "sha256:5e6007b6a9398177a7e1648d04fdd9f8c9766a1a945bceac82f1929e8c78af2d"}, +] + [[package]] name = "paramiko" -version = "3.2.0" +version = "3.3.1" description = "SSH2 protocol library" optional = true python-versions = ">=3.6" files = [ - {file = "paramiko-3.2.0-py3-none-any.whl", hash = "sha256:df0f9dd8903bc50f2e10580af687f3015bf592a377cd438d2ec9546467a14eb8"}, - {file = "paramiko-3.2.0.tar.gz", hash = "sha256:93cdce625a8a1dc12204439d45033f3261bdb2c201648cfcdc06f9fd0f94ec29"}, + {file = "paramiko-3.3.1-py3-none-any.whl", hash = "sha256:b7bc5340a43de4287bbe22fe6de728aa2c22468b2a849615498dd944c2f275eb"}, + {file = "paramiko-3.3.1.tar.gz", hash = "sha256:6a3777a961ac86dbef375c5f5b8d50014a1a96d0fd7f054a43bc880134b0ff77"}, ] [package.dependencies] @@ -1405,13 +1429,13 @@ invoke = ["invoke (>=2.0)"] [[package]] name = "pathspec" -version = "0.11.1" +version = "0.11.2" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.7" files = [ - {file = "pathspec-0.11.1-py3-none-any.whl", hash = "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293"}, - {file = "pathspec-0.11.1.tar.gz", hash = "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687"}, + {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, + {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, ] [[package]] @@ -1438,28 +1462,28 @@ files = [ [[package]] name = "platformdirs" -version = "3.9.1" +version = "3.10.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." optional = false python-versions = ">=3.7" files = [ - {file = "platformdirs-3.9.1-py3-none-any.whl", hash = "sha256:ad8291ae0ae5072f66c16945166cb11c63394c7a3ad1b1bc9828ca3162da8c2f"}, - {file = "platformdirs-3.9.1.tar.gz", hash = "sha256:1b42b450ad933e981d56e59f1b97495428c9bd60698baab9f3eb3d00d5822421"}, + {file = "platformdirs-3.10.0-py3-none-any.whl", hash = "sha256:d7c24979f292f916dc9cbf8648319032f551ea8c49a4c9bf2fb556a02070ec1d"}, + {file = "platformdirs-3.10.0.tar.gz", hash = "sha256:b45696dab2d7cc691a3226759c0d3b00c47c8b6e293d96f6436f733303f77f6d"}, ] [package.extras] -docs = ["furo (>=2023.5.20)", "proselint (>=0.13)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)"] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] [[package]] name = "pluggy" -version = "1.2.0" +version = "1.3.0" description = "plugin and hook calling mechanisms for python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849"}, - {file = "pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3"}, + {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, + {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, ] [package.extras] @@ -1535,13 +1559,13 @@ files = [ [[package]] name = "pygments" -version = "2.15.1" +version = "2.16.1" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.7" files = [ - {file = "Pygments-2.15.1-py3-none-any.whl", hash = "sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1"}, - {file = "Pygments-2.15.1.tar.gz", hash = "sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c"}, + {file = "Pygments-2.16.1-py3-none-any.whl", hash = "sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692"}, + {file = "Pygments-2.16.1.tar.gz", hash = "sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29"}, ] [package.extras] @@ -1549,17 +1573,17 @@ plugins = ["importlib-metadata"] [[package]] name = "pylint" -version = "2.17.4" +version = "2.17.5" description = "python code static checker" optional = false python-versions = ">=3.7.2" files = [ - {file = "pylint-2.17.4-py3-none-any.whl", hash = "sha256:7a1145fb08c251bdb5cca11739722ce64a63db479283d10ce718b2460e54123c"}, - {file = "pylint-2.17.4.tar.gz", hash = "sha256:5dcf1d9e19f41f38e4e85d10f511e5b9c35e1aa74251bf95cdd8cb23584e2db1"}, + {file = "pylint-2.17.5-py3-none-any.whl", hash = "sha256:73995fb8216d3bed149c8d51bba25b2c52a8251a2c8ac846ec668ce38fab5413"}, + {file = "pylint-2.17.5.tar.gz", hash = "sha256:f7b601cbc06fef7e62a754e2b41294c2aa31f1cb659624b9a85bcba29eaf8252"}, ] [package.dependencies] -astroid = ">=2.15.4,<=2.17.0-dev0" +astroid = ">=2.15.6,<=2.17.0-dev0" colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} dill = [ {version = ">=0.2", markers = "python_version < \"3.11\""}, @@ -1578,19 +1602,22 @@ testutils = ["gitpython (>3)"] [[package]] name = "pymdown-extensions" -version = "10.1" +version = "10.2" description = "Extension pack for Python Markdown." optional = false python-versions = ">=3.7" files = [ - {file = "pymdown_extensions-10.1-py3-none-any.whl", hash = "sha256:ef25dbbae530e8f67575d222b75ff0649b1e841e22c2ae9a20bad9472c2207dc"}, - {file = "pymdown_extensions-10.1.tar.gz", hash = "sha256:508009b211373058debb8247e168de4cbcb91b1bff7b5e961b2c3e864e00b195"}, + {file = "pymdown_extensions-10.2-py3-none-any.whl", hash = "sha256:fbb86243db9a681602e3b869deef000211c55d0261015a5cc41d6f34d2afc57f"}, + {file = "pymdown_extensions-10.2.tar.gz", hash = "sha256:06042274876eb4267f12a389daf505eabaebc38bdca26725560c9afda5867549"}, ] [package.dependencies] markdown = ">=3.2" pyyaml = "*" +[package.extras] +extra = ["pygments (>=2.12)"] + [[package]] name = "pynacl" version = "1.5.0" @@ -1619,18 +1646,36 @@ tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"] [[package]] name = "pyparsing" -version = "3.1.0" +version = "3.1.1" description = "pyparsing module - Classes and methods to define and execute parsing grammars" optional = true python-versions = ">=3.6.8" files = [ - {file = "pyparsing-3.1.0-py3-none-any.whl", hash = "sha256:d554a96d1a7d3ddaf7183104485bc19fd80543ad6ac5bdb6426719d766fb06c1"}, - {file = "pyparsing-3.1.0.tar.gz", hash = "sha256:edb662d6fe322d6e990b1594b5feaeadf806803359e3d4d42f11e295e588f0ea"}, + {file = "pyparsing-3.1.1-py3-none-any.whl", hash = "sha256:32c7c0b711493c72ff18a981d24f28aaf9c1fb7ed5e9667c9e84e3db623bdbfb"}, + {file = "pyparsing-3.1.1.tar.gz", hash = "sha256:ede28a1a32462f5a9705e07aea48001a08f7cf81a021585011deba701581a0db"}, ] [package.extras] diagrams = ["jinja2", "railroad-diagrams"] +[[package]] +name = "pyquery" +version = "2.0.0" +description = "A jquery-like library for python" +optional = false +python-versions = "*" +files = [ + {file = "pyquery-2.0.0-py3-none-any.whl", hash = "sha256:8dfc9b4b7c5f877d619bbae74b1898d5743f6ca248cfd5d72b504dd614da312f"}, + {file = "pyquery-2.0.0.tar.gz", hash = "sha256:963e8d4e90262ff6d8dec072ea97285dc374a2f69cad7776f4082abcf6a1d8ae"}, +] + +[package.dependencies] +cssselect = ">=1.2.0" +lxml = ">=2.1" + +[package.extras] +test = ["pytest", "pytest-cov", "requests", "webob", "webtest"] + [[package]] name = "pyserial" version = "3.5" @@ -1704,6 +1749,7 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -1711,8 +1757,15 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -1729,6 +1782,7 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -1736,6 +1790,7 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -1755,6 +1810,21 @@ files = [ [package.dependencies] pyyaml = "*" +[[package]] +name = "readtime" +version = "3.0.0" +description = "Calculates the time some text takes the average human to read, based on Medium's read time forumula" +optional = false +python-versions = "*" +files = [ + {file = "readtime-3.0.0.tar.gz", hash = "sha256:76c5a0d773ad49858c53b42ba3a942f62fbe20cc8c6f07875797ac7dc30963a9"}, +] + +[package.dependencies] +beautifulsoup4 = ">=4.0.1" +markdown2 = ">=2.4.3" +pyquery = ">=1.2" + [[package]] name = "referencing" version = "0.30.2" @@ -1770,6 +1840,103 @@ files = [ attrs = ">=22.2.0" rpds-py = ">=0.7.0" +[[package]] +name = "regex" +version = "2023.8.8" +description = "Alternative regular expression module, to replace re." +optional = false +python-versions = ">=3.6" +files = [ + {file = "regex-2023.8.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:88900f521c645f784260a8d346e12a1590f79e96403971241e64c3a265c8ecdb"}, + {file = "regex-2023.8.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3611576aff55918af2697410ff0293d6071b7e00f4b09e005d614686ac4cd57c"}, + {file = "regex-2023.8.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8a0ccc8f2698f120e9e5742f4b38dc944c38744d4bdfc427616f3a163dd9de5"}, + {file = "regex-2023.8.8-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c662a4cbdd6280ee56f841f14620787215a171c4e2d1744c9528bed8f5816c96"}, + {file = "regex-2023.8.8-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cf0633e4a1b667bfe0bb10b5e53fe0d5f34a6243ea2530eb342491f1adf4f739"}, + {file = "regex-2023.8.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:551ad543fa19e94943c5b2cebc54c73353ffff08228ee5f3376bd27b3d5b9800"}, + {file = "regex-2023.8.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54de2619f5ea58474f2ac211ceea6b615af2d7e4306220d4f3fe690c91988a61"}, + {file = "regex-2023.8.8-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5ec4b3f0aebbbe2fc0134ee30a791af522a92ad9f164858805a77442d7d18570"}, + {file = "regex-2023.8.8-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3ae646c35cb9f820491760ac62c25b6d6b496757fda2d51be429e0e7b67ae0ab"}, + {file = "regex-2023.8.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ca339088839582d01654e6f83a637a4b8194d0960477b9769d2ff2cfa0fa36d2"}, + {file = "regex-2023.8.8-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:d9b6627408021452dcd0d2cdf8da0534e19d93d070bfa8b6b4176f99711e7f90"}, + {file = "regex-2023.8.8-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:bd3366aceedf274f765a3a4bc95d6cd97b130d1dda524d8f25225d14123c01db"}, + {file = "regex-2023.8.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7aed90a72fc3654fba9bc4b7f851571dcc368120432ad68b226bd593f3f6c0b7"}, + {file = "regex-2023.8.8-cp310-cp310-win32.whl", hash = "sha256:80b80b889cb767cc47f31d2b2f3dec2db8126fbcd0cff31b3925b4dc6609dcdb"}, + {file = "regex-2023.8.8-cp310-cp310-win_amd64.whl", hash = "sha256:b82edc98d107cbc7357da7a5a695901b47d6eb0420e587256ba3ad24b80b7d0b"}, + {file = "regex-2023.8.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1e7d84d64c84ad97bf06f3c8cb5e48941f135ace28f450d86af6b6512f1c9a71"}, + {file = "regex-2023.8.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ce0f9fbe7d295f9922c0424a3637b88c6c472b75eafeaff6f910494a1fa719ef"}, + {file = "regex-2023.8.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06c57e14ac723b04458df5956cfb7e2d9caa6e9d353c0b4c7d5d54fcb1325c46"}, + {file = "regex-2023.8.8-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e7a9aaa5a1267125eef22cef3b63484c3241aaec6f48949b366d26c7250e0357"}, + {file = "regex-2023.8.8-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b7408511fca48a82a119d78a77c2f5eb1b22fe88b0d2450ed0756d194fe7a9a"}, + {file = "regex-2023.8.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14dc6f2d88192a67d708341f3085df6a4f5a0c7b03dec08d763ca2cd86e9f559"}, + {file = "regex-2023.8.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48c640b99213643d141550326f34f0502fedb1798adb3c9eb79650b1ecb2f177"}, + {file = "regex-2023.8.8-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0085da0f6c6393428bf0d9c08d8b1874d805bb55e17cb1dfa5ddb7cfb11140bf"}, + {file = "regex-2023.8.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:964b16dcc10c79a4a2be9f1273fcc2684a9eedb3906439720598029a797b46e6"}, + {file = "regex-2023.8.8-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7ce606c14bb195b0e5108544b540e2c5faed6843367e4ab3deb5c6aa5e681208"}, + {file = "regex-2023.8.8-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:40f029d73b10fac448c73d6eb33d57b34607f40116e9f6e9f0d32e9229b147d7"}, + {file = "regex-2023.8.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3b8e6ea6be6d64104d8e9afc34c151926f8182f84e7ac290a93925c0db004bfd"}, + {file = "regex-2023.8.8-cp311-cp311-win32.whl", hash = "sha256:942f8b1f3b223638b02df7df79140646c03938d488fbfb771824f3d05fc083a8"}, + {file = "regex-2023.8.8-cp311-cp311-win_amd64.whl", hash = "sha256:51d8ea2a3a1a8fe4f67de21b8b93757005213e8ac3917567872f2865185fa7fb"}, + {file = "regex-2023.8.8-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e951d1a8e9963ea51efd7f150450803e3b95db5939f994ad3d5edac2b6f6e2b4"}, + {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:704f63b774218207b8ccc6c47fcef5340741e5d839d11d606f70af93ee78e4d4"}, + {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:22283c769a7b01c8ac355d5be0715bf6929b6267619505e289f792b01304d898"}, + {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:91129ff1bb0619bc1f4ad19485718cc623a2dc433dff95baadbf89405c7f6b57"}, + {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de35342190deb7b866ad6ba5cbcccb2d22c0487ee0cbb251efef0843d705f0d4"}, + {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b993b6f524d1e274a5062488a43e3f9f8764ee9745ccd8e8193df743dbe5ee61"}, + {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3026cbcf11d79095a32d9a13bbc572a458727bd5b1ca332df4a79faecd45281c"}, + {file = "regex-2023.8.8-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:293352710172239bf579c90a9864d0df57340b6fd21272345222fb6371bf82b3"}, + {file = "regex-2023.8.8-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:d909b5a3fff619dc7e48b6b1bedc2f30ec43033ba7af32f936c10839e81b9217"}, + {file = "regex-2023.8.8-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:3d370ff652323c5307d9c8e4c62efd1956fb08051b0e9210212bc51168b4ff56"}, + {file = "regex-2023.8.8-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:b076da1ed19dc37788f6a934c60adf97bd02c7eea461b73730513921a85d4235"}, + {file = "regex-2023.8.8-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e9941a4ada58f6218694f382e43fdd256e97615db9da135e77359da257a7168b"}, + {file = "regex-2023.8.8-cp36-cp36m-win32.whl", hash = "sha256:a8c65c17aed7e15a0c824cdc63a6b104dfc530f6fa8cb6ac51c437af52b481c7"}, + {file = "regex-2023.8.8-cp36-cp36m-win_amd64.whl", hash = "sha256:aadf28046e77a72f30dcc1ab185639e8de7f4104b8cb5c6dfa5d8ed860e57236"}, + {file = "regex-2023.8.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:423adfa872b4908843ac3e7a30f957f5d5282944b81ca0a3b8a7ccbbfaa06103"}, + {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ae594c66f4a7e1ea67232a0846649a7c94c188d6c071ac0210c3e86a5f92109"}, + {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e51c80c168074faa793685656c38eb7a06cbad7774c8cbc3ea05552d615393d8"}, + {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:09b7f4c66aa9d1522b06e31a54f15581c37286237208df1345108fcf4e050c18"}, + {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e73e5243af12d9cd6a9d6a45a43570dbe2e5b1cdfc862f5ae2b031e44dd95a8"}, + {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:941460db8fe3bd613db52f05259c9336f5a47ccae7d7def44cc277184030a116"}, + {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f0ccf3e01afeb412a1a9993049cb160d0352dba635bbca7762b2dc722aa5742a"}, + {file = "regex-2023.8.8-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:2e9216e0d2cdce7dbc9be48cb3eacb962740a09b011a116fd7af8c832ab116ca"}, + {file = "regex-2023.8.8-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:5cd9cd7170459b9223c5e592ac036e0704bee765706445c353d96f2890e816c8"}, + {file = "regex-2023.8.8-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:4873ef92e03a4309b3ccd8281454801b291b689f6ad45ef8c3658b6fa761d7ac"}, + {file = "regex-2023.8.8-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:239c3c2a339d3b3ddd51c2daef10874410917cd2b998f043c13e2084cb191684"}, + {file = "regex-2023.8.8-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1005c60ed7037be0d9dea1f9c53cc42f836188227366370867222bda4c3c6bd7"}, + {file = "regex-2023.8.8-cp37-cp37m-win32.whl", hash = "sha256:e6bd1e9b95bc5614a7a9c9c44fde9539cba1c823b43a9f7bc11266446dd568e3"}, + {file = "regex-2023.8.8-cp37-cp37m-win_amd64.whl", hash = "sha256:9a96edd79661e93327cfeac4edec72a4046e14550a1d22aa0dd2e3ca52aec921"}, + {file = "regex-2023.8.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f2181c20ef18747d5f4a7ea513e09ea03bdd50884a11ce46066bb90fe4213675"}, + {file = "regex-2023.8.8-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a2ad5add903eb7cdde2b7c64aaca405f3957ab34f16594d2b78d53b8b1a6a7d6"}, + {file = "regex-2023.8.8-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9233ac249b354c54146e392e8a451e465dd2d967fc773690811d3a8c240ac601"}, + {file = "regex-2023.8.8-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:920974009fb37b20d32afcdf0227a2e707eb83fe418713f7a8b7de038b870d0b"}, + {file = "regex-2023.8.8-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2b6c5dfe0929b6c23dde9624483380b170b6e34ed79054ad131b20203a1a63"}, + {file = "regex-2023.8.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96979d753b1dc3b2169003e1854dc67bfc86edf93c01e84757927f810b8c3c93"}, + {file = "regex-2023.8.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2ae54a338191e1356253e7883d9d19f8679b6143703086245fb14d1f20196be9"}, + {file = "regex-2023.8.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2162ae2eb8b079622176a81b65d486ba50b888271302190870b8cc488587d280"}, + {file = "regex-2023.8.8-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c884d1a59e69e03b93cf0dfee8794c63d7de0ee8f7ffb76e5f75be8131b6400a"}, + {file = "regex-2023.8.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:cf9273e96f3ee2ac89ffcb17627a78f78e7516b08f94dc435844ae72576a276e"}, + {file = "regex-2023.8.8-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:83215147121e15d5f3a45d99abeed9cf1fe16869d5c233b08c56cdf75f43a504"}, + {file = "regex-2023.8.8-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:3f7454aa427b8ab9101f3787eb178057c5250478e39b99540cfc2b889c7d0586"}, + {file = "regex-2023.8.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f0640913d2c1044d97e30d7c41728195fc37e54d190c5385eacb52115127b882"}, + {file = "regex-2023.8.8-cp38-cp38-win32.whl", hash = "sha256:0c59122ceccb905a941fb23b087b8eafc5290bf983ebcb14d2301febcbe199c7"}, + {file = "regex-2023.8.8-cp38-cp38-win_amd64.whl", hash = "sha256:c12f6f67495ea05c3d542d119d270007090bad5b843f642d418eb601ec0fa7be"}, + {file = "regex-2023.8.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:82cd0a69cd28f6cc3789cc6adeb1027f79526b1ab50b1f6062bbc3a0ccb2dbc3"}, + {file = "regex-2023.8.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bb34d1605f96a245fc39790a117ac1bac8de84ab7691637b26ab2c5efb8f228c"}, + {file = "regex-2023.8.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:987b9ac04d0b38ef4f89fbc035e84a7efad9cdd5f1e29024f9289182c8d99e09"}, + {file = "regex-2023.8.8-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9dd6082f4e2aec9b6a0927202c85bc1b09dcab113f97265127c1dc20e2e32495"}, + {file = "regex-2023.8.8-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7eb95fe8222932c10d4436e7a6f7c99991e3fdd9f36c949eff16a69246dee2dc"}, + {file = "regex-2023.8.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7098c524ba9f20717a56a8d551d2ed491ea89cbf37e540759ed3b776a4f8d6eb"}, + {file = "regex-2023.8.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b694430b3f00eb02c594ff5a16db30e054c1b9589a043fe9174584c6efa8033"}, + {file = "regex-2023.8.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b2aeab3895d778155054abea5238d0eb9a72e9242bd4b43f42fd911ef9a13470"}, + {file = "regex-2023.8.8-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:988631b9d78b546e284478c2ec15c8a85960e262e247b35ca5eaf7ee22f6050a"}, + {file = "regex-2023.8.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:67ecd894e56a0c6108ec5ab1d8fa8418ec0cff45844a855966b875d1039a2e34"}, + {file = "regex-2023.8.8-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:14898830f0a0eb67cae2bbbc787c1a7d6e34ecc06fbd39d3af5fe29a4468e2c9"}, + {file = "regex-2023.8.8-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:f2200e00b62568cfd920127782c61bc1c546062a879cdc741cfcc6976668dfcf"}, + {file = "regex-2023.8.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9691a549c19c22d26a4f3b948071e93517bdf86e41b81d8c6ac8a964bb71e5a6"}, + {file = "regex-2023.8.8-cp39-cp39-win32.whl", hash = "sha256:6ab2ed84bf0137927846b37e882745a827458689eb969028af8032b1b3dac78e"}, + {file = "regex-2023.8.8-cp39-cp39-win_amd64.whl", hash = "sha256:5543c055d8ec7801901e1193a51570643d6a6ab8751b1f7dd9af71af467538bb"}, + {file = "regex-2023.8.8.tar.gz", hash = "sha256:fcbdc5f2b0f1cd0f6a56cdb46fe41d2cce1e644e3b68832f3eeebc5fb0f7712e"}, +] + [[package]] name = "requests" version = "2.31.0" @@ -1793,13 +1960,13 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "rich" -version = "13.4.2" +version = "13.5.2" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.7.0" files = [ - {file = "rich-13.4.2-py3-none-any.whl", hash = "sha256:8f87bc7ee54675732fa66a05ebfe489e27264caeeff3728c945d25971b6485ec"}, - {file = "rich-13.4.2.tar.gz", hash = "sha256:d653d6bccede5844304c605d5aac802c7cf9621efd700b46c7ec2b51ea914898"}, + {file = "rich-13.5.2-py3-none-any.whl", hash = "sha256:146a90b3b6b47cac4a73c12866a499e9817426423f57c5a66949c086191a8808"}, + {file = "rich-13.5.2.tar.gz", hash = "sha256:fb9d6c0a0f643c99eed3875b5377a184132ba9be4d61516a55273d3554d75a39"}, ] [package.dependencies] @@ -1812,108 +1979,108 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"] [[package]] name = "rpds-py" -version = "0.9.2" +version = "0.10.0" description = "Python bindings to Rust's persistent data structures (rpds)" optional = true python-versions = ">=3.8" files = [ - {file = "rpds_py-0.9.2-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:ab6919a09c055c9b092798ce18c6c4adf49d24d4d9e43a92b257e3f2548231e7"}, - {file = "rpds_py-0.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d55777a80f78dd09410bd84ff8c95ee05519f41113b2df90a69622f5540c4f8b"}, - {file = "rpds_py-0.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a216b26e5af0a8e265d4efd65d3bcec5fba6b26909014effe20cd302fd1138fa"}, - {file = "rpds_py-0.9.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:29cd8bfb2d716366a035913ced99188a79b623a3512292963d84d3e06e63b496"}, - {file = "rpds_py-0.9.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:44659b1f326214950a8204a248ca6199535e73a694be8d3e0e869f820767f12f"}, - {file = "rpds_py-0.9.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:745f5a43fdd7d6d25a53ab1a99979e7f8ea419dfefebcab0a5a1e9095490ee5e"}, - {file = "rpds_py-0.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a987578ac5214f18b99d1f2a3851cba5b09f4a689818a106c23dbad0dfeb760f"}, - {file = "rpds_py-0.9.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bf4151acb541b6e895354f6ff9ac06995ad9e4175cbc6d30aaed08856558201f"}, - {file = "rpds_py-0.9.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:03421628f0dc10a4119d714a17f646e2837126a25ac7a256bdf7c3943400f67f"}, - {file = "rpds_py-0.9.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:13b602dc3e8dff3063734f02dcf05111e887f301fdda74151a93dbbc249930fe"}, - {file = "rpds_py-0.9.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fae5cb554b604b3f9e2c608241b5d8d303e410d7dfb6d397c335f983495ce7f6"}, - {file = "rpds_py-0.9.2-cp310-none-win32.whl", hash = "sha256:47c5f58a8e0c2c920cc7783113df2fc4ff12bf3a411d985012f145e9242a2764"}, - {file = "rpds_py-0.9.2-cp310-none-win_amd64.whl", hash = "sha256:4ea6b73c22d8182dff91155af018b11aac9ff7eca085750455c5990cb1cfae6e"}, - {file = "rpds_py-0.9.2-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:e564d2238512c5ef5e9d79338ab77f1cbbda6c2d541ad41b2af445fb200385e3"}, - {file = "rpds_py-0.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f411330a6376fb50e5b7a3e66894e4a39e60ca2e17dce258d53768fea06a37bd"}, - {file = "rpds_py-0.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e7521f5af0233e89939ad626b15278c71b69dc1dfccaa7b97bd4cdf96536bb7"}, - {file = "rpds_py-0.9.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8d3335c03100a073883857e91db9f2e0ef8a1cf42dc0369cbb9151c149dbbc1b"}, - {file = "rpds_py-0.9.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d25b1c1096ef0447355f7293fbe9ad740f7c47ae032c2884113f8e87660d8f6e"}, - {file = "rpds_py-0.9.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a5d3fbd02efd9cf6a8ffc2f17b53a33542f6b154e88dd7b42ef4a4c0700fdad"}, - {file = "rpds_py-0.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c5934e2833afeaf36bd1eadb57256239785f5af0220ed8d21c2896ec4d3a765f"}, - {file = "rpds_py-0.9.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:095b460e117685867d45548fbd8598a8d9999227e9061ee7f012d9d264e6048d"}, - {file = "rpds_py-0.9.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:91378d9f4151adc223d584489591dbb79f78814c0734a7c3bfa9c9e09978121c"}, - {file = "rpds_py-0.9.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:24a81c177379300220e907e9b864107614b144f6c2a15ed5c3450e19cf536fae"}, - {file = "rpds_py-0.9.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:de0b6eceb46141984671802d412568d22c6bacc9b230174f9e55fc72ef4f57de"}, - {file = "rpds_py-0.9.2-cp311-none-win32.whl", hash = "sha256:700375326ed641f3d9d32060a91513ad668bcb7e2cffb18415c399acb25de2ab"}, - {file = "rpds_py-0.9.2-cp311-none-win_amd64.whl", hash = "sha256:0766babfcf941db8607bdaf82569ec38107dbb03c7f0b72604a0b346b6eb3298"}, - {file = "rpds_py-0.9.2-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:b1440c291db3f98a914e1afd9d6541e8fc60b4c3aab1a9008d03da4651e67386"}, - {file = "rpds_py-0.9.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0f2996fbac8e0b77fd67102becb9229986396e051f33dbceada3debaacc7033f"}, - {file = "rpds_py-0.9.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f30d205755566a25f2ae0382944fcae2f350500ae4df4e795efa9e850821d82"}, - {file = "rpds_py-0.9.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:159fba751a1e6b1c69244e23ba6c28f879a8758a3e992ed056d86d74a194a0f3"}, - {file = "rpds_py-0.9.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a1f044792e1adcea82468a72310c66a7f08728d72a244730d14880cd1dabe36b"}, - {file = "rpds_py-0.9.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9251eb8aa82e6cf88510530b29eef4fac825a2b709baf5b94a6094894f252387"}, - {file = "rpds_py-0.9.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01899794b654e616c8625b194ddd1e5b51ef5b60ed61baa7a2d9c2ad7b2a4238"}, - {file = "rpds_py-0.9.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0c43f8ae8f6be1d605b0465671124aa8d6a0e40f1fb81dcea28b7e3d87ca1e1"}, - {file = "rpds_py-0.9.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:207f57c402d1f8712618f737356e4b6f35253b6d20a324d9a47cb9f38ee43a6b"}, - {file = "rpds_py-0.9.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b52e7c5ae35b00566d244ffefba0f46bb6bec749a50412acf42b1c3f402e2c90"}, - {file = "rpds_py-0.9.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:978fa96dbb005d599ec4fd9ed301b1cc45f1a8f7982d4793faf20b404b56677d"}, - {file = "rpds_py-0.9.2-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:6aa8326a4a608e1c28da191edd7c924dff445251b94653988efb059b16577a4d"}, - {file = "rpds_py-0.9.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:aad51239bee6bff6823bbbdc8ad85136c6125542bbc609e035ab98ca1e32a192"}, - {file = "rpds_py-0.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4bd4dc3602370679c2dfb818d9c97b1137d4dd412230cfecd3c66a1bf388a196"}, - {file = "rpds_py-0.9.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dd9da77c6ec1f258387957b754f0df60766ac23ed698b61941ba9acccd3284d1"}, - {file = "rpds_py-0.9.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:190ca6f55042ea4649ed19c9093a9be9d63cd8a97880106747d7147f88a49d18"}, - {file = "rpds_py-0.9.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:876bf9ed62323bc7dcfc261dbc5572c996ef26fe6406b0ff985cbcf460fc8a4c"}, - {file = "rpds_py-0.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa2818759aba55df50592ecbc95ebcdc99917fa7b55cc6796235b04193eb3c55"}, - {file = "rpds_py-0.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9ea4d00850ef1e917815e59b078ecb338f6a8efda23369677c54a5825dbebb55"}, - {file = "rpds_py-0.9.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:5855c85eb8b8a968a74dc7fb014c9166a05e7e7a8377fb91d78512900aadd13d"}, - {file = "rpds_py-0.9.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:14c408e9d1a80dcb45c05a5149e5961aadb912fff42ca1dd9b68c0044904eb32"}, - {file = "rpds_py-0.9.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:65a0583c43d9f22cb2130c7b110e695fff834fd5e832a776a107197e59a1898e"}, - {file = "rpds_py-0.9.2-cp38-none-win32.whl", hash = "sha256:71f2f7715935a61fa3e4ae91d91b67e571aeb5cb5d10331ab681256bda2ad920"}, - {file = "rpds_py-0.9.2-cp38-none-win_amd64.whl", hash = "sha256:674c704605092e3ebbbd13687b09c9f78c362a4bc710343efe37a91457123044"}, - {file = "rpds_py-0.9.2-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:07e2c54bef6838fa44c48dfbc8234e8e2466d851124b551fc4e07a1cfeb37260"}, - {file = "rpds_py-0.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f7fdf55283ad38c33e35e2855565361f4bf0abd02470b8ab28d499c663bc5d7c"}, - {file = "rpds_py-0.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:890ba852c16ace6ed9f90e8670f2c1c178d96510a21b06d2fa12d8783a905193"}, - {file = "rpds_py-0.9.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:50025635ba8b629a86d9d5474e650da304cb46bbb4d18690532dd79341467846"}, - {file = "rpds_py-0.9.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:517cbf6e67ae3623c5127206489d69eb2bdb27239a3c3cc559350ef52a3bbf0b"}, - {file = "rpds_py-0.9.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0836d71ca19071090d524739420a61580f3f894618d10b666cf3d9a1688355b1"}, - {file = "rpds_py-0.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c439fd54b2b9053717cca3de9583be6584b384d88d045f97d409f0ca867d80f"}, - {file = "rpds_py-0.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f68996a3b3dc9335037f82754f9cdbe3a95db42bde571d8c3be26cc6245f2324"}, - {file = "rpds_py-0.9.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7d68dc8acded354c972116f59b5eb2e5864432948e098c19fe6994926d8e15c3"}, - {file = "rpds_py-0.9.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:f963c6b1218b96db85fc37a9f0851eaf8b9040aa46dec112611697a7023da535"}, - {file = "rpds_py-0.9.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5a46859d7f947061b4010e554ccd1791467d1b1759f2dc2ec9055fa239f1bc26"}, - {file = "rpds_py-0.9.2-cp39-none-win32.whl", hash = "sha256:e07e5dbf8a83c66783a9fe2d4566968ea8c161199680e8ad38d53e075df5f0d0"}, - {file = "rpds_py-0.9.2-cp39-none-win_amd64.whl", hash = "sha256:682726178138ea45a0766907957b60f3a1bf3acdf212436be9733f28b6c5af3c"}, - {file = "rpds_py-0.9.2-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:196cb208825a8b9c8fc360dc0f87993b8b260038615230242bf18ec84447c08d"}, - {file = "rpds_py-0.9.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:c7671d45530fcb6d5e22fd40c97e1e1e01965fc298cbda523bb640f3d923b387"}, - {file = "rpds_py-0.9.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83b32f0940adec65099f3b1c215ef7f1d025d13ff947975a055989cb7fd019a4"}, - {file = "rpds_py-0.9.2-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7f67da97f5b9eac838b6980fc6da268622e91f8960e083a34533ca710bec8611"}, - {file = "rpds_py-0.9.2-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:03975db5f103997904c37e804e5f340c8fdabbb5883f26ee50a255d664eed58c"}, - {file = "rpds_py-0.9.2-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:987b06d1cdb28f88a42e4fb8a87f094e43f3c435ed8e486533aea0bf2e53d931"}, - {file = "rpds_py-0.9.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c861a7e4aef15ff91233751619ce3a3d2b9e5877e0fcd76f9ea4f6847183aa16"}, - {file = "rpds_py-0.9.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:02938432352359805b6da099c9c95c8a0547fe4b274ce8f1a91677401bb9a45f"}, - {file = "rpds_py-0.9.2-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:ef1f08f2a924837e112cba2953e15aacfccbbfcd773b4b9b4723f8f2ddded08e"}, - {file = "rpds_py-0.9.2-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:35da5cc5cb37c04c4ee03128ad59b8c3941a1e5cd398d78c37f716f32a9b7f67"}, - {file = "rpds_py-0.9.2-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:141acb9d4ccc04e704e5992d35472f78c35af047fa0cfae2923835d153f091be"}, - {file = "rpds_py-0.9.2-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:79f594919d2c1a0cc17d1988a6adaf9a2f000d2e1048f71f298b056b1018e872"}, - {file = "rpds_py-0.9.2-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:a06418fe1155e72e16dddc68bb3780ae44cebb2912fbd8bb6ff9161de56e1798"}, - {file = "rpds_py-0.9.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b2eb034c94b0b96d5eddb290b7b5198460e2d5d0c421751713953a9c4e47d10"}, - {file = "rpds_py-0.9.2-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8b08605d248b974eb02f40bdcd1a35d3924c83a2a5e8f5d0fa5af852c4d960af"}, - {file = "rpds_py-0.9.2-pp38-pypy38_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a0805911caedfe2736935250be5008b261f10a729a303f676d3d5fea6900c96a"}, - {file = "rpds_py-0.9.2-pp38-pypy38_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ab2299e3f92aa5417d5e16bb45bb4586171c1327568f638e8453c9f8d9e0f020"}, - {file = "rpds_py-0.9.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c8d7594e38cf98d8a7df25b440f684b510cf4627fe038c297a87496d10a174f"}, - {file = "rpds_py-0.9.2-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8b9ec12ad5f0a4625db34db7e0005be2632c1013b253a4a60e8302ad4d462afd"}, - {file = "rpds_py-0.9.2-pp38-pypy38_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:1fcdee18fea97238ed17ab6478c66b2095e4ae7177e35fb71fbe561a27adf620"}, - {file = "rpds_py-0.9.2-pp38-pypy38_pp73-musllinux_1_2_i686.whl", hash = "sha256:933a7d5cd4b84f959aedeb84f2030f0a01d63ae6cf256629af3081cf3e3426e8"}, - {file = "rpds_py-0.9.2-pp38-pypy38_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:686ba516e02db6d6f8c279d1641f7067ebb5dc58b1d0536c4aaebb7bf01cdc5d"}, - {file = "rpds_py-0.9.2-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:0173c0444bec0a3d7d848eaeca2d8bd32a1b43f3d3fde6617aac3731fa4be05f"}, - {file = "rpds_py-0.9.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:d576c3ef8c7b2d560e301eb33891d1944d965a4d7a2eacb6332eee8a71827db6"}, - {file = "rpds_py-0.9.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed89861ee8c8c47d6beb742a602f912b1bb64f598b1e2f3d758948721d44d468"}, - {file = "rpds_py-0.9.2-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1054a08e818f8e18910f1bee731583fe8f899b0a0a5044c6e680ceea34f93876"}, - {file = "rpds_py-0.9.2-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99e7c4bb27ff1aab90dcc3e9d37ee5af0231ed98d99cb6f5250de28889a3d502"}, - {file = "rpds_py-0.9.2-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c545d9d14d47be716495076b659db179206e3fd997769bc01e2d550eeb685596"}, - {file = "rpds_py-0.9.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9039a11bca3c41be5a58282ed81ae422fa680409022b996032a43badef2a3752"}, - {file = "rpds_py-0.9.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fb39aca7a64ad0c9490adfa719dbeeb87d13be137ca189d2564e596f8ba32c07"}, - {file = "rpds_py-0.9.2-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:2d8b3b3a2ce0eaa00c5bbbb60b6713e94e7e0becab7b3db6c5c77f979e8ed1f1"}, - {file = "rpds_py-0.9.2-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:99b1c16f732b3a9971406fbfe18468592c5a3529585a45a35adbc1389a529a03"}, - {file = "rpds_py-0.9.2-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:c27ee01a6c3223025f4badd533bea5e87c988cb0ba2811b690395dfe16088cfe"}, - {file = "rpds_py-0.9.2.tar.gz", hash = "sha256:8d70e8f14900f2657c249ea4def963bed86a29b81f81f5b76b5a9215680de945"}, + {file = "rpds_py-0.10.0-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:c1e0e9916301e3b3d970814b1439ca59487f0616d30f36a44cead66ee1748c31"}, + {file = "rpds_py-0.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8ce8caa29ebbdcde67e5fd652c811d34bc01f249dbc0d61e5cc4db05ae79a83b"}, + {file = "rpds_py-0.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad277f74b1c164f7248afa968700e410651eb858d7c160d109fb451dc45a2f09"}, + {file = "rpds_py-0.10.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8e1c68303ccf7fceb50fbab79064a2636119fd9aca121f28453709283dbca727"}, + {file = "rpds_py-0.10.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:780fcb855be29153901c67fc9c5633d48aebef21b90aa72812fa181d731c6b00"}, + {file = "rpds_py-0.10.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bbd7b24d108509a1b9b6679fcc1166a7dd031dbef1f3c2c73788f42e3ebb3beb"}, + {file = "rpds_py-0.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0700c2133ba203c4068aaecd6a59bda22e06a5e46255c9da23cbf68c6942215d"}, + {file = "rpds_py-0.10.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:576da63eae7809f375932bfcbca2cf20620a1915bf2fedce4b9cc8491eceefe3"}, + {file = "rpds_py-0.10.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:23750a9b8a329844ba1fe267ca456bb3184984da2880ed17ae641c5af8de3fef"}, + {file = "rpds_py-0.10.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:d08395595c42bcd82c3608762ce734504c6d025eef1c06f42326a6023a584186"}, + {file = "rpds_py-0.10.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1d7b7b71bcb82d8713c7c2e9c5f061415598af5938666beded20d81fa23e7640"}, + {file = "rpds_py-0.10.0-cp310-none-win32.whl", hash = "sha256:97f5811df21703446b42303475b8b855ee07d6ab6cdf8565eff115540624f25d"}, + {file = "rpds_py-0.10.0-cp310-none-win_amd64.whl", hash = "sha256:cdbed8f21204398f47de39b0a9b180d7e571f02dfb18bf5f1b618e238454b685"}, + {file = "rpds_py-0.10.0-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:7a3a3d3e4f1e3cd2a67b93a0b6ed0f2499e33f47cc568e3a0023e405abdc0ff1"}, + {file = "rpds_py-0.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fc72ae476732cdb7b2c1acb5af23b478b8a0d4b6fcf19b90dd150291e0d5b26b"}, + {file = "rpds_py-0.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c0583f69522732bdd79dca4cd3873e63a29acf4a299769c7541f2ca1e4dd4bc6"}, + {file = "rpds_py-0.10.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f8b9a7cd381970e64849070aca7c32d53ab7d96c66db6c2ef7aa23c6e803f514"}, + {file = "rpds_py-0.10.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0d292cabd7c8335bdd3237ded442480a249dbcdb4ddfac5218799364a01a0f5c"}, + {file = "rpds_py-0.10.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6903cdca64f1e301af9be424798328c1fe3b4b14aede35f04510989fc72f012"}, + {file = "rpds_py-0.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bed57543c99249ab3a4586ddc8786529fbc33309e5e8a1351802a06ca2baf4c2"}, + {file = "rpds_py-0.10.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:15932ec5f224b0e35764dc156514533a4fca52dcfda0dfbe462a1a22b37efd59"}, + {file = "rpds_py-0.10.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:eb2d59bc196e6d3b1827c7db06c1a898bfa0787c0574af398e65ccf2e97c0fbe"}, + {file = "rpds_py-0.10.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f99d74ddf9d3b6126b509e81865f89bd1283e3fc1b568b68cd7bd9dfa15583d7"}, + {file = "rpds_py-0.10.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f70bec8a14a692be6dbe7ce8aab303e88df891cbd4a39af091f90b6702e28055"}, + {file = "rpds_py-0.10.0-cp311-none-win32.whl", hash = "sha256:5f7487be65b9c2c510819e744e375bd41b929a97e5915c4852a82fbb085df62c"}, + {file = "rpds_py-0.10.0-cp311-none-win_amd64.whl", hash = "sha256:748e472345c3a82cfb462d0dff998a7bf43e621eed73374cb19f307e97e08a83"}, + {file = "rpds_py-0.10.0-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:d4639111e73997567343df6551da9dd90d66aece1b9fc26c786d328439488103"}, + {file = "rpds_py-0.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f4760e1b02173f4155203054f77a5dc0b4078de7645c922b208d28e7eb99f3e2"}, + {file = "rpds_py-0.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a6420a36975e0073acaeee44ead260c1f6ea56812cfc6c31ec00c1c48197173"}, + {file = "rpds_py-0.10.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:58fc4d66ee349a23dbf08c7e964120dc9027059566e29cf0ce6205d590ed7eca"}, + {file = "rpds_py-0.10.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:063411228b852fb2ed7485cf91f8e7d30893e69b0acb207ec349db04cccc8225"}, + {file = "rpds_py-0.10.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65af12f70355de29e1092f319f85a3467f4005e959ab65129cb697169ce94b86"}, + {file = "rpds_py-0.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:298e8b5d8087e0330aac211c85428c8761230ef46a1f2c516d6a2f67fb8803c5"}, + {file = "rpds_py-0.10.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5b9bf77008f2c55dabbd099fd3ac87009471d223a1c7ebea36873d39511b780a"}, + {file = "rpds_py-0.10.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c7853f27195598e550fe089f78f0732c66ee1d1f0eaae8ad081589a5a2f5d4af"}, + {file = "rpds_py-0.10.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:75dbfd41a61bc1fb0536bf7b1abf272dc115c53d4d77db770cd65d46d4520882"}, + {file = "rpds_py-0.10.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b25136212a3d064a8f0b9ebbb6c57094c5229e0de76d15c79b76feff26aeb7b8"}, + {file = "rpds_py-0.10.0-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:9affee8cb1ec453382c27eb9043378ab32f49cd4bc24a24275f5c39bf186c279"}, + {file = "rpds_py-0.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4d55528ef13af4b4e074d067977b1f61408602f53ae4537dccf42ba665c2c7bd"}, + {file = "rpds_py-0.10.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7865df1fb564092bcf46dac61b5def25342faf6352e4bc0e61a286e3fa26a3d"}, + {file = "rpds_py-0.10.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3f5cc8c7bc99d2bbcd704cef165ca7d155cd6464c86cbda8339026a42d219397"}, + {file = "rpds_py-0.10.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cbae50d352e4717ffc22c566afc2d0da744380e87ed44a144508e3fb9114a3f4"}, + {file = "rpds_py-0.10.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fccbf0cd3411719e4c9426755df90bf3449d9fc5a89f077f4a7f1abd4f70c910"}, + {file = "rpds_py-0.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78d10c431073dc6ebceed35ab22948a016cc2b5120963c13a41e38bdde4a7212"}, + {file = "rpds_py-0.10.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1b401e8b9aece651512e62c431181e6e83048a651698a727ea0eb0699e9f9b74"}, + {file = "rpds_py-0.10.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:7618a082c55cf038eede4a918c1001cc8a4411dfe508dc762659bcd48d8f4c6e"}, + {file = "rpds_py-0.10.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:b3226b246facae14909b465061ddcfa2dfeadb6a64f407f24300d42d69bcb1a1"}, + {file = "rpds_py-0.10.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:a8edd467551c1102dc0f5754ab55cd0703431cd3044edf8c8e7d9208d63fa453"}, + {file = "rpds_py-0.10.0-cp38-none-win32.whl", hash = "sha256:71333c22f7cf5f0480b59a0aef21f652cf9bbaa9679ad261b405b65a57511d1e"}, + {file = "rpds_py-0.10.0-cp38-none-win_amd64.whl", hash = "sha256:a8ab1adf04ae2d6d65835995218fd3f3eb644fe20655ca8ee233e2c7270ff53b"}, + {file = "rpds_py-0.10.0-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:87c93b25d538c433fb053da6228c6290117ba53ff6a537c133b0f2087948a582"}, + {file = "rpds_py-0.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e7996aed3f65667c6dcc8302a69368435a87c2364079a066750a2eac75ea01e"}, + {file = "rpds_py-0.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8856aa76839dc234d3469f1e270918ce6bec1d6a601eba928f45d68a15f04fc3"}, + {file = "rpds_py-0.10.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:00215f6a9058fbf84f9d47536902558eb61f180a6b2a0fa35338d06ceb9a2e5a"}, + {file = "rpds_py-0.10.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:23a059143c1393015c68936370cce11690f7294731904bdae47cc3e16d0b2474"}, + {file = "rpds_py-0.10.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3e5c26905aa651cc8c0ddc45e0e5dea2a1296f70bdc96af17aee9d0493280a17"}, + {file = "rpds_py-0.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c651847545422c8131660704c58606d841e228ed576c8f1666d98b3d318f89da"}, + {file = "rpds_py-0.10.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:80992eb20755701753e30a6952a96aa58f353d12a65ad3c9d48a8da5ec4690cf"}, + {file = "rpds_py-0.10.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ffcf18ad3edf1c170e27e88b10282a2c449aa0358659592462448d71b2000cfc"}, + {file = "rpds_py-0.10.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:08e08ccf5b10badb7d0a5c84829b914c6e1e1f3a716fdb2bf294e2bd01562775"}, + {file = "rpds_py-0.10.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7150b83b3e3ddaac81a8bb6a9b5f93117674a0e7a2b5a5b32ab31fdfea6df27f"}, + {file = "rpds_py-0.10.0-cp39-none-win32.whl", hash = "sha256:3455ecc46ea443b5f7d9c2f946ce4017745e017b0d0f8b99c92564eff97e97f5"}, + {file = "rpds_py-0.10.0-cp39-none-win_amd64.whl", hash = "sha256:afe6b5a04b2ab1aa89bad32ca47bf71358e7302a06fdfdad857389dca8fb5f04"}, + {file = "rpds_py-0.10.0-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:b1cb078f54af0abd835ca76f93a3152565b73be0f056264da45117d0adf5e99c"}, + {file = "rpds_py-0.10.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8e7e2b3577e97fa43c2c2b12a16139b2cedbd0770235d5179c0412b4794efd9b"}, + {file = "rpds_py-0.10.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae46a50d235f1631d9ec4670503f7b30405103034830bc13df29fd947207f795"}, + {file = "rpds_py-0.10.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f869e34d2326e417baee430ae998e91412cc8e7fdd83d979277a90a0e79a5b47"}, + {file = "rpds_py-0.10.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3d544a614055b131111bed6edfa1cb0fb082a7265761bcb03321f2dd7b5c6c48"}, + {file = "rpds_py-0.10.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ee9c2f6ca9774c2c24bbf7b23086264e6b5fa178201450535ec0859739e6f78d"}, + {file = "rpds_py-0.10.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2da4a8c6d465fde36cea7d54bf47b5cf089073452f0e47c8632ecb9dec23c07"}, + {file = "rpds_py-0.10.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ac00c41dd315d147b129976204839ca9de699d83519ff1272afbe4fb9d362d12"}, + {file = "rpds_py-0.10.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:0155c33af0676fc38e1107679be882077680ad1abb6303956b97259c3177e85e"}, + {file = "rpds_py-0.10.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:db6585b600b2e76e98131e0ac0e5195759082b51687ad0c94505970c90718f4a"}, + {file = "rpds_py-0.10.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:7b6975d3763d0952c111700c0634968419268e6bbc0b55fe71138987fa66f309"}, + {file = "rpds_py-0.10.0-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:6388e4e95a26717b94a05ced084e19da4d92aca883f392dffcf8e48c8e221a24"}, + {file = "rpds_py-0.10.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:18f87baa20e02e9277ad8960cd89b63c79c05caf106f4c959a9595c43f2a34a5"}, + {file = "rpds_py-0.10.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92f05fc7d832e970047662b3440b190d24ea04f8d3c760e33e7163b67308c878"}, + {file = "rpds_py-0.10.0-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:291c9ce3929a75b45ce8ddde2aa7694fc8449f2bc8f5bd93adf021efaae2d10b"}, + {file = "rpds_py-0.10.0-pp38-pypy38_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:861d25ae0985a1dd5297fee35f476b60c6029e2e6e19847d5b4d0a43a390b696"}, + {file = "rpds_py-0.10.0-pp38-pypy38_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:668d2b45d62c68c7a370ac3dce108ffda482b0a0f50abd8b4c604a813a59e08f"}, + {file = "rpds_py-0.10.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:344b89384c250ba6a4ce1786e04d01500e4dac0f4137ceebcaad12973c0ac0b3"}, + {file = "rpds_py-0.10.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:885e023e73ce09b11b89ab91fc60f35d80878d2c19d6213a32b42ff36543c291"}, + {file = "rpds_py-0.10.0-pp38-pypy38_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:841128a22e6ac04070a0f84776d07e9c38c4dcce8e28792a95e45fc621605517"}, + {file = "rpds_py-0.10.0-pp38-pypy38_pp73-musllinux_1_2_i686.whl", hash = "sha256:899b5e7e2d5a8bc92aa533c2d4e55e5ebba095c485568a5e4bedbc163421259a"}, + {file = "rpds_py-0.10.0-pp38-pypy38_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:e7947d9a6264c727a556541b1630296bbd5d0a05068d21c38dde8e7a1c703ef0"}, + {file = "rpds_py-0.10.0-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:4992266817169997854f81df7f6db7bdcda1609972d8ffd6919252f09ec3c0f6"}, + {file = "rpds_py-0.10.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:26d9fd624649a10e4610fab2bc820e215a184d193e47d0be7fe53c1c8f67f370"}, + {file = "rpds_py-0.10.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0028eb0967942d0d2891eae700ae1a27b7fd18604cfcb16a1ef486a790fee99e"}, + {file = "rpds_py-0.10.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f9e7e493ded7042712a374471203dd43ae3fff5b81e3de1a0513fa241af9fd41"}, + {file = "rpds_py-0.10.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2d68a8e8a3a816629283faf82358d8c93fe5bd974dd2704152394a3de4cec22a"}, + {file = "rpds_py-0.10.0-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d6d5f061f6a2aa55790b9e64a23dfd87b6664ab56e24cd06c78eb43986cb260b"}, + {file = "rpds_py-0.10.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c7c4266c1b61eb429e8aeb7d8ed6a3bfe6c890a1788b18dbec090c35c6b93fa"}, + {file = "rpds_py-0.10.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:80772e3bda6787510d9620bc0c7572be404a922f8ccdfd436bf6c3778119464c"}, + {file = "rpds_py-0.10.0-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:b98e75b21fc2ba5285aef8efaf34131d16af1c38df36bdca2f50634bea2d3060"}, + {file = "rpds_py-0.10.0-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:d63787f289944cc4bde518ad2b5e70a4f0d6e2ce76324635359c74c113fd188f"}, + {file = "rpds_py-0.10.0-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:872f3dcaa8bf2245944861d7311179d2c0c9b2aaa7d3b464d99a7c2e401f01fa"}, + {file = "rpds_py-0.10.0.tar.gz", hash = "sha256:e36d7369363d2707d5f68950a64c4e025991eb0177db01ccb6aa6facae48b69f"}, ] [[package]] @@ -1932,18 +2099,18 @@ paramiko = "*" [[package]] name = "setuptools" -version = "68.0.0" +version = "68.1.2" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = true -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "setuptools-68.0.0-py3-none-any.whl", hash = "sha256:11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f"}, - {file = "setuptools-68.0.0.tar.gz", hash = "sha256:baf1fdb41c6da4cd2eae722e135500da913332ab3f2f5c7d33af9b492acb5235"}, + {file = "setuptools-68.1.2-py3-none-any.whl", hash = "sha256:3d8083eed2d13afc9426f227b24fd1659489ec107c0e86cec2ffdde5c92e790b"}, + {file = "setuptools-68.1.2.tar.gz", hash = "sha256:3d4dfa6d95f1b101d695a6160a7626e15583af71a5f52176efa5d39a054d475d"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5,<=7.1.2)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] @@ -1980,162 +2147,16 @@ files = [ ] [[package]] -name = "sphinx" -version = "6.2.1" -description = "Python documentation generator" -optional = false -python-versions = ">=3.8" -files = [ - {file = "Sphinx-6.2.1.tar.gz", hash = "sha256:6d56a34697bb749ffa0152feafc4b19836c755d90a7c59b72bc7dfd371b9cc6b"}, - {file = "sphinx-6.2.1-py3-none-any.whl", hash = "sha256:97787ff1fa3256a3eef9eda523a63dbf299f7b47e053cfcf684a1c2a8380c912"}, -] - -[package.dependencies] -alabaster = ">=0.7,<0.8" -babel = ">=2.9" -colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} -docutils = ">=0.18.1,<0.20" -imagesize = ">=1.3" -importlib-metadata = {version = ">=4.8", markers = "python_version < \"3.10\""} -Jinja2 = ">=3.0" -packaging = ">=21.0" -Pygments = ">=2.13" -requests = ">=2.25.0" -snowballstemmer = ">=2.0" -sphinxcontrib-applehelp = "*" -sphinxcontrib-devhelp = "*" -sphinxcontrib-htmlhelp = ">=2.0.0" -sphinxcontrib-jsmath = "*" -sphinxcontrib-qthelp = "*" -sphinxcontrib-serializinghtml = ">=1.1.5" - -[package.extras] -docs = ["sphinxcontrib-websupport"] -lint = ["docutils-stubs", "flake8 (>=3.5.0)", "flake8-simplify", "isort", "mypy (>=0.990)", "ruff", "sphinx-lint", "types-requests"] -test = ["cython", "filelock", "html5lib", "pytest (>=4.6)"] - -[[package]] -name = "sphinx-rtd-theme" -version = "1.2.2" -description = "Read the Docs theme for Sphinx" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" -files = [ - {file = "sphinx_rtd_theme-1.2.2-py2.py3-none-any.whl", hash = "sha256:6a7e7d8af34eb8fc57d52a09c6b6b9c46ff44aea5951bc831eeb9245378f3689"}, - {file = "sphinx_rtd_theme-1.2.2.tar.gz", hash = "sha256:01c5c5a72e2d025bd23d1f06c59a4831b06e6ce6c01fdd5ebfe9986c0a880fc7"}, -] - -[package.dependencies] -docutils = "<0.19" -sphinx = ">=1.6,<7" -sphinxcontrib-jquery = ">=4,<5" - -[package.extras] -dev = ["bump2version", "sphinxcontrib-httpdomain", "transifex-client", "wheel"] - -[[package]] -name = "sphinxcontrib-applehelp" -version = "1.0.4" -description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" -optional = false -python-versions = ">=3.8" -files = [ - {file = "sphinxcontrib-applehelp-1.0.4.tar.gz", hash = "sha256:828f867945bbe39817c210a1abfd1bc4895c8b73fcaade56d45357a348a07d7e"}, - {file = "sphinxcontrib_applehelp-1.0.4-py3-none-any.whl", hash = "sha256:29d341f67fb0f6f586b23ad80e072c8e6ad0b48417db2bde114a4c9746feb228"}, -] - -[package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -test = ["pytest"] - -[[package]] -name = "sphinxcontrib-devhelp" -version = "1.0.2" -description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." -optional = false -python-versions = ">=3.5" -files = [ - {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"}, - {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"}, -] - -[package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -test = ["pytest"] - -[[package]] -name = "sphinxcontrib-htmlhelp" -version = "2.0.1" -description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" -optional = false -python-versions = ">=3.8" -files = [ - {file = "sphinxcontrib-htmlhelp-2.0.1.tar.gz", hash = "sha256:0cbdd302815330058422b98a113195c9249825d681e18f11e8b1f78a2f11efff"}, - {file = "sphinxcontrib_htmlhelp-2.0.1-py3-none-any.whl", hash = "sha256:c38cb46dccf316c79de6e5515e1770414b797162b23cd3d06e67020e1d2a6903"}, -] - -[package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -test = ["html5lib", "pytest"] - -[[package]] -name = "sphinxcontrib-jquery" -version = "4.1" -description = "Extension to include jQuery on newer Sphinx releases" -optional = false -python-versions = ">=2.7" -files = [ - {file = "sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a"}, - {file = "sphinxcontrib_jquery-4.1-py2.py3-none-any.whl", hash = "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae"}, -] - -[package.dependencies] -Sphinx = ">=1.8" - -[[package]] -name = "sphinxcontrib-jsmath" -version = "1.0.1" -description = "A sphinx extension which renders display math in HTML via JavaScript" -optional = false -python-versions = ">=3.5" -files = [ - {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, - {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, -] - -[package.extras] -test = ["flake8", "mypy", "pytest"] - -[[package]] -name = "sphinxcontrib-qthelp" -version = "1.0.3" -description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." +name = "soupsieve" +version = "2.4.1" +description = "A modern CSS selector implementation for Beautiful Soup." optional = false -python-versions = ">=3.5" -files = [ - {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"}, - {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"}, -] - -[package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -test = ["pytest"] - -[[package]] -name = "sphinxcontrib-serializinghtml" -version = "1.1.5" -description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." -optional = false -python-versions = ">=3.5" +python-versions = ">=3.7" files = [ - {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"}, - {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"}, + {file = "soupsieve-2.4.1-py3-none-any.whl", hash = "sha256:1c1bfee6819544a3447586c889157365a27e10d88cde3ad3da0cf0ddf646feb8"}, + {file = "soupsieve-2.4.1.tar.gz", hash = "sha256:89d12b2d5dfcd2c9e8c22326da9d9aa9cb3dfab0a83a024f05704076ee8d35ea"}, ] -[package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -test = ["pytest"] - [[package]] name = "stevedore" version = "5.1.0" @@ -2189,13 +2210,13 @@ files = [ [[package]] name = "tomlkit" -version = "0.11.8" +version = "0.12.1" description = "Style preserving TOML library" optional = false python-versions = ">=3.7" files = [ - {file = "tomlkit-0.11.8-py3-none-any.whl", hash = "sha256:8c726c4c202bdb148667835f68d68780b9a003a9ec34167b6c673b38eff2a171"}, - {file = "tomlkit-0.11.8.tar.gz", hash = "sha256:9330fc7faa1db67b541b28e62018c17d20be733177d290a13b24c62d1614e0c3"}, + {file = "tomlkit-0.12.1-py3-none-any.whl", hash = "sha256:712cbd236609acc6a3e2e97253dfc52d4c2082982a88f61b640ecf0817eab899"}, + {file = "tomlkit-0.12.1.tar.gz", hash = "sha256:38e1ff8edb991273ec9f6181244a6a391ac30e9f5098e7535640ea6be97a7c86"}, ] [[package]] @@ -2317,13 +2338,13 @@ watchmedo = ["PyYAML (>=3.10)"] [[package]] name = "wheel" -version = "0.41.0" +version = "0.41.2" description = "A built-package format for Python" optional = false python-versions = ">=3.7" files = [ - {file = "wheel-0.41.0-py3-none-any.whl", hash = "sha256:7e9be3bbd0078f6147d82ed9ed957e323e7708f57e134743d2edef3a7b7972a9"}, - {file = "wheel-0.41.0.tar.gz", hash = "sha256:55a0f0a5a84869bce5ba775abfd9c462e3a6b1b7b7ec69d72c0b83d673a5114d"}, + {file = "wheel-0.41.2-py3-none-any.whl", hash = "sha256:75909db2664838d015e3d9139004ee16711748a52c8f336b52882266540215d8"}, + {file = "wheel-0.41.2.tar.gz", hash = "sha256:0c5ac5ff2afb79ac23ab82bab027a0be7b5dbcf2e54dc50efe4bf507de1f7985"}, ] [package.extras] @@ -2465,4 +2486,4 @@ optionals = ["jsonschema", "napalm"] [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "f738c1bfadb7e1d5cc6d9166b372979c83e4c44aec704c781adb9f3375b61b35" +content-hash = "edbe4200862a8ff8d2c55b57de0a0c7b1b58acbe0275bea2d1444bc2059c1728" diff --git a/pyproject.toml b/pyproject.toml index 5bebe5a4..3f93198d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,7 +32,7 @@ jsonschema = {version = "^4.17.3", optional = true} [tool.poetry.extras] optionals = ["jsonschema", "napalm"] -[tool.poetry.dev-dependencies] +[tool.poetry.group.dev.dependencies] bandit = "*" black = "*" coverage = "*" @@ -42,20 +42,19 @@ pylint = "*" pytest = "*" pyyaml = "*" pydocstyle = "*" -sphinx-rtd-theme = "*" toml = "*" yamllint = "*" mypy = "*" -mkdocs = "1.4.3" -mkdocs-material = "8.5.8" -mkdocstrings = "0.22.0" -mkdocstrings-python = "1.4.0" +mkdocs = "1.5.2" +mkdocs-material = "9.2.4" mkdocs-version-annotations = "1.0.0" +mkdocstrings = "0.22.0" +mkdocstrings-python = "1.6.0" mkdocs-python-classy = "0.1.3" [tool.black] line-length = 120 -target-version = ['py311'] +target-version = ['py38', 'py39', 'py310', 'py311'] include = '\.pyi?$' exclude = ''' ( diff --git a/tests/unit/test_basics.py b/tests/unit/test_basics.py index e5339cf6..451f8869 100644 --- a/tests/unit/test_basics.py +++ b/tests/unit/test_basics.py @@ -8,9 +8,10 @@ class TestDocsPackaging(unittest.TestCase): """Test Version in doc requirements is the same pyproject.""" def test_version(self): - """Verify that pyproject.toml dev dependecies have the same versions as in the docs requirements.txt.""" + """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_details = toml.load(os.path.join(parent_path, "pyproject.toml"))["tool"]["poetry"]["dev-dependencies"] + 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: From 7b9e159ce79322869eb78352b6700ddf96d994ef Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 31 Aug 2023 14:19:28 -0500 Subject: [PATCH 15/21] Flat: latest data (2023-08-31T08:03:18.660Z) (#357) { "date": "2023-08-31T08:03:18.660Z", "files": [ { "name": "netutils/data_files/oui_mappings.py", "deltaBytes": 3273, "source": "https://standards-oui.ieee.org" } ] } Co-authored-by: flat-data --- netutils/data_files/oui_mappings.py | 82 ++++++++++++++++++++++++++++- 1 file changed, 80 insertions(+), 2 deletions(-) diff --git a/netutils/data_files/oui_mappings.py b/netutils/data_files/oui_mappings.py index be42a658..c3efe54d 100644 --- a/netutils/data_files/oui_mappings.py +++ b/netutils/data_files/oui_mappings.py @@ -6868,7 +6868,7 @@ "001b06": "Ateliers R. LAUMONIER", "001b07": "Mendocino Software", "001b08": "Danfoss Drives A/S", - "001b09": "Matrix Telecom Pvt. Ltd.", + "001b09": "MATRIX COMSEC PRIVATE LIMITED", "001b0a": "Intelligent Distributed Controls Ltd", "001b0b": "Phidgets Inc.", "001b0c": "Cisco Systems, Inc", @@ -8031,7 +8031,7 @@ "001f99": "SERONICS co.ltd", "001f9a": "Nortel Networks", "001f9b": "POSBRO", - "001f9c": "LEDCO", + "001f9c": "Havis Inc.", "001f9d": "Cisco Systems, Inc", "001f9e": "Cisco Systems, Inc", "001f9f": "Thomson Telecom Belgium", @@ -10523,6 +10523,7 @@ "00464b": "HUAWEI TECHNOLOGIES CO.,LTD", "004a77": "zte corporation", "004bf3": "SHENZHEN MERCURY COMMUNICATION TECHNOLOGIES CO.,LTD.", + "004ce5": "Sichuan Tianyi Comheart Telecom Co.,LTD", "004d32": "Andon Health Co.,Ltd.", "004e01": "Dell Inc.", "004e35": "Hewlett Packard Enterprise", @@ -12298,6 +12299,7 @@ "00c610": "Apple, Inc.", "00c711": "ITEL MOBILE LIMITED", "00c88b": "Cisco Systems, Inc", + "00c896": "CIG SHANGHAI CO LTD", "00cae5": "Cisco Systems, Inc", "00cb00": "Private", "00cb51": "Sagemcom Broadband SAS", @@ -14672,6 +14674,7 @@ "14ca56": "zte corporation", "14caa0": "Hu&Co", "14cb19": "HP Inc.", + "14cb49": "Habolink Technology Co.,LTD", "14cb65": "Microsoft Corporation", "14cc20": "TP-LINK TECHNOLOGIES CO.,LTD.", "14ccb3": "AO GK NATEKS", @@ -15121,6 +15124,7 @@ "1c1fd4": "LifeBEAM Technologies LTD", "1c1ff1": "Huawei Device Co., Ltd.", "1c20db": "HUAWEI TECHNOLOGIES CO.,LTD", + "1c2156": "Smappee NV", "1c21d1": "IEEE Registration Authority", "1c2285": "Serrature Meroni SpA", "1c232c": "Samsung Electronics Co.,Ltd", @@ -15731,6 +15735,7 @@ "2400fa": "Information Technology Co., Ltd", "24016f": "Huawei Device Co., Ltd.", "2401c7": "Cisco Systems, Inc", + "240462": "Siemens Energy Global GmbH & Co.KG - GT PRM", "24050f": "MTN Electronic Co. Ltd", "240588": "Google, Inc.", "2405f5": "Sdn. Bhd.", @@ -15793,6 +15798,7 @@ "2429fe": "KYOCERA Corporation ", "242a04": "Cisco Systems, Inc", "242cfe": "Zhejiang Tmall Technology Co., Ltd.", + "242d6c": "eero inc.", "242e02": "HUAWEI TECHNOLOGIES CO.,LTD", "242e90": "PALIT MICROSYSTEMS, LTD", "242ffa": "Toshiba Global Commerce Solutions", @@ -16044,6 +16050,7 @@ "24fd5b": "SmartThings, Inc.", "24fdfa": "Private", "24fe9a": "CyberTAN Technology Inc.", + "2800af": "Dell Inc.", "28011c": "zte corporation", "28022e": "Apple, Inc.", "280244": "Apple, Inc.", @@ -16139,6 +16146,7 @@ "284c53": "Intune Networks", "284d92": "Luminator", "284ed7": "OutSmart Power Systems, Inc.", + "284ee9": "mercury corperation", "284fce": "Liaoning Wontel Science and Technology Development Co.,Ltd.", "285132": "Shenzhen Prayfly Technology Co.,Ltd", "285261": "Cisco Systems, Inc", @@ -16593,6 +16601,7 @@ "2c9d65": "vivo Mobile Communication Co., Ltd.", "2c9e00": "Sony Interactive Entertainment Inc.", "2c9e5f": "ARRIS Group, Inc.", + "2c9ee0": "Cavli Inc.", "2c9eec": "Jabil Circuit Penang", "2c9efc": "CANON INC.", "2c9ffb": "Wistron Neweb Corporation", @@ -16648,6 +16657,7 @@ "2cc5d3": "Ruckus Wireless", "2cc6a0": "Lumacron Technology Ltd.", "2cc81b": "Routerboard.com", + "2cc8f5": "Huawei Device Co., Ltd.", "2cca0c": "WITHUS PLANET", "2cca75": "Robert Bosch GmbH AnP", "2ccc15": "Nokia Corporation", @@ -16947,6 +16957,7 @@ "30c750": "MIC Technology Group", "30c7ae": "Samsung Electronics Co.,Ltd", "30c82a": "WI-BIZ srl", + "30c922": "Espressif Inc.", "30c9ab": "CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD.", "30cb36": "Belden Singapore Pte. Ltd.", "30cbc7": "Cambium Networks Limited", @@ -16967,6 +16978,7 @@ "30d7a1": "Apple, Inc.", "30d875": "Apple, Inc.", "30d941": "Raydium Semiconductor Corp.", + "30d97f": "Tech4home, Lda", "30d9d9": "Apple, Inc.", "30de4b": "TP-Link Corporation Limited", "30de86": "Cedac Software S.r.l.", @@ -17529,6 +17541,7 @@ "3886f7": "Google, Inc.", "3887d5": "Intel Corporate", "38881e": "HUAWEI TECHNOLOGIES CO.,LTD", + "388871": "ASKEY COMPUTER CORP", "3888a4": "Apple, Inc.", "38892c": "Apple, Inc.", "3889dc": "Opticon Sensors Europe B.V.", @@ -17940,6 +17953,7 @@ "3cab8e": "Apple, Inc.", "3cae69": "ESA Elektroschaltanlagen Grimma GmbH", "3cb07e": "Arounds Intelligent Equipment Co., Ltd.", + "3cb0ed": "Nothing Technology Limited", "3cb15b": "Avaya Inc", "3cb17f": "Wattwatchers Pty Ld", "3cb233": "Huawei Device Co., Ltd.", @@ -18163,6 +18177,7 @@ "4062b6": "Tele system communication", "4062ea": "China Mobile Group Device Co.,Ltd.", "4064a4": "THE FURUKAWA ELECTRIC CO., LTD", + "4064dc": "X-speed lnformation Technology Co.,Ltd", "4065a3": "Sagemcom Broadband SAS", "40667a": "mediola - connected living AG", "40679b": "Shenzhen Skyworth Digital Technology CO., Ltd", @@ -18190,6 +18205,7 @@ "407d0f": "HUAWEI TECHNOLOGIES CO.,LTD", "407f5f": "Juniper Networks", "407fe0": "Limited", + "4080e1": "FN-LINK TECHNOLOGY Ltd.", "408256": "Continental Automotive GmbH", "40831d": "Apple, Inc.", "4083de": "Zebra Technologies Inc", @@ -18265,6 +18281,7 @@ "40b688": "LEGIC Identsystems AG", "40b6b1": "SUNGSAM CO,.Ltd", "40b6e7": "Huawei Device Co., Ltd.", + "40b70e": "Huawei Device Co., Ltd.", "40b7f3": "ARRIS Group, Inc.", "40b7fc": "Phyplus Microelectronics Limited", "40b837": "Sony Corporation", @@ -18830,6 +18847,7 @@ "4861a3": "Concern Axion JSC", "4861ee": "Samsung Electronics Co.,Ltd", "486276": "HUAWEI TECHNOLOGIES CO.,LTD", + "486345": "Huawei Device Co., Ltd.", "4865ee": "IEEE Registration Authority", "486834": "Silicon Motion, Inc.", "48684a": "Intel Corporate", @@ -18998,6 +19016,7 @@ "48e1e9": "Chengdu Meross Technology Co., Ltd.", "48e244": "Hon Hai Precision Ind. Co.,Ltd.", "48e3c3": "JENOPTIK Advanced Systems GmbH", + "48e533": "Sunnovo International Limited", "48e695": "Insigma Inc", "48e6c0": "SIMCom Wireless Solutions Co.,Ltd.", "48e729": "Espressif Inc.", @@ -19006,6 +19025,7 @@ "48ea63": "Zhejiang Uniview Technologies Co., Ltd.", "48eb30": "ETERNA TECHNOLOGY, INC.", "48eb62": "Murata Manufacturing Co., Ltd.", + "48eb65": "Henan KunLun Technologies CO.,Ltd.", "48ec5b": "Nokia Solutions and Networks GmbH & Co. KG", "48ed80": "daesung eltec", "48ede6": "Zyxel Communications Corporation", @@ -19128,6 +19148,7 @@ "4c4e35": "Cisco Systems, Inc", "4c4fee": "Co., Ltd", "4c5077": "Huawei Device Co., Ltd.", + "4c50dd": "Hui Zhou Gaoshengda Technology Co.,LTD", "4c50f1": "GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD", "4c5262": "Fujitsu Technology Solutions GmbH", "4c52ec": "SOLARWATT GmbH", @@ -19140,6 +19161,7 @@ "4c55cc": "Zentri Pty Ltd", "4c569d": "Apple, Inc.", "4c56df": "Targus US LLC", + "4c5739": "Samsung Electronics Co.,Ltd", "4c57ca": "Apple, Inc.", "4c5bb3": "Silicon Laboratories", "4c5cdf": "ITEL MOBILE LIMITED", @@ -19149,6 +19171,7 @@ "4c5ed3": "Unisyue Technologies Co; LTD.", "4c5f70": "Intel Corporate", "4c5fd2": "Alcatel-Lucent", + "4c60ba": "AltoBeam Inc.", "4c60d5": "airPointe of New Hampshire", "4c60de": "NETGEAR", "4c617e": "Huawei Device Co., Ltd.", @@ -19944,6 +19967,7 @@ "54b7e5": "Rayson Technology Co., Ltd.", "54b802": "Samsung Electronics Co.,Ltd", "54b80a": "D-Link International", + "54b874": "GD Midea Air-Conditioning Equipment Co.,Ltd.", "54bad6": "HUAWEI TECHNOLOGIES CO.,LTD", "54bd79": "Samsung Electronics Co.,Ltd", "54be53": "zte corporation", @@ -20289,6 +20313,7 @@ "58e16c": "Ying Hua Information Technology (Shanghai)Co., LTD", "58e28f": "Apple, Inc.", "58e326": "Compass Technologies Inc.", + "58e359": "Interroll Software & Electronics GmbH", "58e403": "Wistron Neweb Corporation", "58e434": "Juniper Networks", "58e476": "CENTRON COMMUNICATIONS TECHNOLOGIES FUJIAN CO.,LTD", @@ -20567,6 +20592,7 @@ "5cc5d4": "Intel Corporate", "5cc6d0": "Co.,Ltd", "5cc6e9": "Edifier International", + "5cc787": "Huawei Device Co., Ltd.", "5cc7c1": "Silicon Laboratories", "5cc7d7": "AZROAD TECHNOLOGY COMPANY LIMITED", "5cc8e3": "Shintec Hozumi co.ltd.", @@ -20640,6 +20666,7 @@ "5cf7c3": "TECHNOLOGY LIMITED", "5cf7e6": "Apple, Inc.", "5cf821": "Texas Instruments", + "5cf838": "IEEE Registration Authority", "5cf8a1": "Murata Manufacturing Co., Ltd.", "5cf938": "Apple, Inc.", "5cf96a": "HUAWEI TECHNOLOGIES CO.,LTD", @@ -20792,6 +20819,7 @@ "60684e": "Samsung Electronics Co.,Ltd", "606944": "Apple, Inc.", "60699b": "isepos GmbH", + "606b5b": "Arista Network, Inc.", "606bbd": "Samsung Electronics Co.,Ltd", "606bff": "Nintendo Co.,Ltd", "606c63": "Hitron Technologies. Inc", @@ -21103,6 +21131,7 @@ "6450d6": "Liquidtool Systems", "645106": "Hewlett Packard", "64517e": "ELECTRONIC TECHNOLOGY CO.,LTD.", + "6451f4": "Huawei Device Co., Ltd.", "645234": "Sichuan Tianyi Comheart Telecom Co.,LTD", "645299": "The Chamberlain Group, Inc", "64535d": "Frauscher Sensortechnik", @@ -21246,6 +21275,7 @@ "64bc58": "Intel Corporate", "64be63": "STORDIS GmbH", "64bf6b": "HUAWEI TECHNOLOGIES CO.,LTD", + "64c17e": "cheilelectric", "64c269": "eero inc.", "64c354": "Avaya Inc", "64c394": "HUAWEI TECHNOLOGIES CO.,LTD", @@ -21518,6 +21548,7 @@ "689320": "New H3C Technologies Co., Ltd", "689361": "Sdn. Bhd.", "689423": "Hon Hai Precision Ind. Co.,Ltd.", + "68944a": "zte corporation", "68962e": "HUAWEI TECHNOLOGIES CO.,LTD", "68966a": "OHSUNG", "68967b": "Apple, Inc.", @@ -21684,6 +21715,7 @@ "6c1544": "Microsoft Corporation", "6c15f9": "Nautronix Limited", "6c160e": "ShotTracker", + "6c1629": "Edifier International", "6c1632": "HUAWEI TECHNOLOGIES CO.,LTD", "6c1811": "Decatur Electronics", "6c198f": "D-Link International", @@ -22203,6 +22235,7 @@ "708cbb": "MIMODISPLAYKOREA", "708d09": "Nokia Corporation", "708f47": "vivo Mobile Communication Co., Ltd.", + "709041": "Mist Systems, Inc.", "7090b7": "Huawei Device Co., Ltd.", "70918f": "Weber-Stephen Products LLC", "7091f3": "Universal Electronics, Inc.", @@ -22403,6 +22436,7 @@ "7432c2": "KYOLIS", "743357": "vivo Mobile Communication Co., Ltd.", "7433a6": "Shenzhen SuperElectron Technology Co.,Ltd.", + "7433e9": "zte corporation", "743400": "MTG Co., Ltd.", "74342b": "HUAWEI TECHNOLOGIES CO.,LTD", "7434ae": "this is engineering Inc.", @@ -22830,6 +22864,7 @@ "78593e": "RAFI GmbH & Co.KG", "78595e": "Samsung Electronics Co.,Ltd", "785968": "Hon Hai Precision Ind. Co.,Ltd.", + "785994": "Alif Semiconductor, Inc.", "785b64": "Huawei Device Co., Ltd.", "785c28": "Prime Motion Inc.", "785c5e": "HUAWEI TECHNOLOGIES CO.,LTD", @@ -23045,6 +23080,7 @@ "78eb46": "HUAWEI TECHNOLOGIES CO.,LTD", "78ec22": "Shanghai Qihui Telecom Technology Co., LTD", "78ec74": "Kyland-USA", + "78edbc": "Co., Ltd", "78ef4c": "Unetconvergence Co., Ltd.", "78f09b": "Huawei Device Co., Ltd.", "78f1c6": "Cisco Systems, Inc", @@ -23332,6 +23368,7 @@ "7cc3a1": "Apple, Inc.", "7cc4ef": "Devialet", "7cc537": "Apple, Inc.", + "7cc6b6": "Silicon Laboratories", "7cc6c4": "Kolff Computer Supplies b.v.", "7cc709": "SHENZHEN RF-LINK TECHNOLOGY CO.,LTD.", "7cc74a": "Fiberhome Telecommunication Technologies Co.,LTD", @@ -23848,6 +23885,7 @@ "844bf5": "Hon Hai Precision Ind. Co.,Ltd.", "844dbe": "Fiberhome Telecommunication Technologies Co.,LTD", "844f03": "Ablelink Electronics Ltd", + "845075": "Huawei Device Co., Ltd.", "84509a": "Easy Soft TV Co., Ltd", "845181": "Samsung Electronics Co.,Ltd", "8454df": "Huawei Device Co., Ltd.", @@ -23879,6 +23917,7 @@ "846b48": "ShenZhen EepuLink Co., Ltd.", "846eb1": "Park Assist LLC", "846fce": "GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD", + "847051": "ALPSALPINE CO,.LTD", "8470d7": "eero inc.", "847127": "Silicon Laboratories", "84716a": "Huawei Device Co., Ltd.", @@ -24417,6 +24456,7 @@ "8c04ff": "Vantiva USA LLC", "8c0551": "Koubachi AG", "8c06cb": "Toradex AG", + "8c0734": "Private", "8c078c": "FLOW DATA INC", "8c088b": "Remote Solution", "8c09f4": "ARRIS Group, Inc.", @@ -24463,6 +24503,7 @@ "8c2f39": "IBA Dosimetry GmbH", "8c2fa6": "Solid Optics B.V.", "8c31e2": "DAYOUPLUS", + "8c3223": "JWIPC Technology Co.,Ltd.", "8c3330": "EmFirst Co., Ltd.", "8c3357": "HiteVision Digital Media Technology Co.,Ltd.", "8c3401": "GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD", @@ -24991,6 +25032,7 @@ "90a7c1": "Pakedge Device and Software Inc.", "90a822": "Amazon Technologies Inc.", "90a935": "JWEntertainment", + "90a9f7": "IEEE Registration Authority", "90aac3": "Hitron Technologies. Inc", "90ab96": "Silicon Laboratories", "90ac3f": "BrightSign LLC", @@ -25162,6 +25204,7 @@ "942e63": "Finsécur", "94319b": "Alphatronics BV", "9431cb": "vivo Mobile Communication Co., Ltd.", + "943251": "ASKEY COMPUTER CORP", "9433dd": "Taco Inc", "943469": "Silicon Laboratories", "94350a": "Samsung Electronics Co.,Ltd", @@ -25292,6 +25335,7 @@ "94a3ca": "KonnectONE, LLC", "94a408": "Shenzhen Trolink Technology CO, LTD", "94a40c": "Diehl Metering GmbH", + "94a4b5": "Mitsubishi Electric Corporation", "94a4f9": "HUAWEI TECHNOLOGIES CO.,LTD", "94a67e": "NETGEAR", "94a7b7": "zte corporation", @@ -25308,6 +25352,7 @@ "94aef0": "Cisco Systems, Inc", "94b01f": "Apple, Inc.", "94b10a": "Samsung Electronics Co.,Ltd", + "94b216": "Silicon Laboratories", "94b271": "HUAWEI TECHNOLOGIES CO.,LTD", "94b2cc": "PIONEER CORPORATION", "94b34f": "Ruckus Wireless", @@ -25442,6 +25487,7 @@ "98038a": "Texas Instruments", "98039b": "Mellanox Technologies, Inc.", "9803a0": "ABB n.v. Power Quality Products", + "9803cf": "SHENZHEN BILIAN ELECTRONIC CO.,LTD", "9803d8": "Apple, Inc.", "980637": "IEEE Registration Authority", "98063a": "Home Control Singapore Pte Ltd", @@ -25478,6 +25524,7 @@ "98226e": "Amazon Technologies Inc.", "9822ef": "Liteon Technology Corporation", "98234e": "Micromedia AG", + "98254a": "Big Field Global PTE. Ltd.", "98262a": "Applied Research Associates, Inc", "9826ad": "Quectel Wireless Solutions Co.,Ltd.", "982782": "IEEE Registration Authority", @@ -25772,6 +25819,7 @@ "9c1d58": "Texas Instruments", "9c1e95": "Actiontec Electronics, Inc", "9c1ea4": "Sdn. Bhd.", + "9c1ece": "ALT Co., Ltd.", "9c1ecf": "Valeo Telematik und Akustik GmbH", "9c1fca": "Hangzhou AlmightyDigit Technology Co., Ltd", "9c1fdd": "Accupix Inc.", @@ -25809,6 +25857,7 @@ "9c3583": "Nipro Diagnostics, Inc", "9c35eb": "Apple, Inc.", "9c36f8": "Hyundai Kefico", + "9c37cb": "Sony Interactive Entertainment Inc.", "9c37f4": "HUAWEI TECHNOLOGIES CO.,LTD", "9c3928": "Samsung Electronics Co.,Ltd", "9c3a9a": "Shenzhen Sundray Technologies Company Limited", @@ -26484,6 +26533,7 @@ "a43523": "Guangdong Donyan Network Technologies Co.,Ltd.", "a4352d": "TRIZ Networks corp.", "a436c7": "LG Innotek", + "a4373e": "Huawei Device Co., Ltd.", "a43831": "RF elements s.r.o.", "a438cc": "Nintendo Co.,Ltd", "a438fc": "Plastic Logic", @@ -26826,6 +26876,7 @@ "a84041": "Dragino Technology Co., Limited", "a8407d": "GD Midea Air-Conditioning Equipment Co.,Ltd.", "a84122": "Information Technology Co.,Ltd.", + "a841f4": "AzureWave Technology Inc.", "a842a1": "TP-Link Corporation Limited", "a842a7": "Jiangsu Huitong Group Co.,Ltd.", "a842e3": "Espressif Inc.", @@ -27134,6 +27185,7 @@ "ac34cb": "Shanhai GBCOM Communication Technology Co. Ltd", "ac35ee": "FN-LINK TECHNOLOGY LIMITED", "ac3613": "Samsung Electronics Co.,Ltd", + "ac361b": "Hon Hai Precision Industry Co.,LTD", "ac3651": "Jiangsu Hengtong Terahertz Technology Co., Ltd.", "ac3728": "Taicang T&W Electronics", "ac3743": "HTC Corporation", @@ -27467,6 +27519,7 @@ "b0285b": "JUHUA Technology Inc.", "b02a1f": "Wingtech Group (HongKong)Limited", "b02a43": "Google, Inc.", + "b02ee0": "Huawei Device Co., Ltd.", "b03055": "China Mobile IOT Company Limited", "b030c8": "Teal Drones, Inc.", "b03366": "vivo Mobile Communication Co., Ltd.", @@ -28068,6 +28121,7 @@ "b80305": "Intel Corporate", "b80415": "Bayan Audio", "b805ab": "zte corporation", + "b8060d": "Tuya Smart Inc.", "b80716": "vivo Mobile Communication Co., Ltd.", "b80756": "Cisco Meraki", "b808cf": "Intel Corporate", @@ -28709,6 +28763,7 @@ "bcf685": "D-Link International", "bcf730": "Samsung Electronics Co.,Ltd", "bcf811": "Xiamen DNAKE Technology Co.,Ltd", + "bcf87e": "Arcadyan Corporation", "bcf88b": "zte corporation", "bcf9f2": "TEKO", "bcfab8": "Guangzhou Shiyuan Electronic Technology Company Limited", @@ -28881,6 +28936,7 @@ "c08c71": "Motorola Mobility LLC, a Lenovo Company", "c08d51": "Amazon Technologies Inc.", "c08f20": "Shenzhen Skyworth Digital Technology CO., Ltd", + "c09120": "Sichuan Tianyi Comheart Telecom Co.,LTD", "c09132": "Patriot Memory", "c09134": "ProCurve Networking by HP", "c091b9": "Amazon Technologies Inc.", @@ -29134,6 +29190,7 @@ "c44e1f": "BlueN", "c44eac": "Shenzhen Shiningworth Technology Co., Ltd.", "c44f33": "Espressif Inc.", + "c44f5f": "Huawei Device Co., Ltd.", "c44f96": "Alps Alpine", "c45006": "Samsung Electronics Co.,Ltd", "c4518d": "Shenzhen YOUHUA Technology Co., Ltd", @@ -29146,6 +29203,7 @@ "c456fe": "Lava International Ltd.", "c4571f": "June Life Inc", "c4576e": "Samsung Electronics Co.,Ltd", + "c45781": "Limited", "c458c2": "Shenzhen TATFOOK Technology Co., Ltd.", "c45976": "Fugoo Coorporation", "c45a86": "Huawei Device Co., Ltd.", @@ -29237,6 +29295,7 @@ "c493d9": "Samsung Electronics Co.,Ltd", "c49500": "Amazon Technologies Inc.", "c4954d": "IEEE Registration Authority", + "c4955f": "Anhui Saida Technology Limited Liability Company", "c495a2": "SHENZHEN WEIJIU INDUSTRY AND TRADE DEVELOPMENT CO., LTD", "c49805": "Minieum Networks, Inc", "c4985c": "Hui Zhou Gaoshengda Technology Co.,LTD", @@ -29300,6 +29359,7 @@ "c4cad9": "Hangzhou H3C Technologies Co., Limited", "c4cb54": "Fibocom Auto Inc.", "c4cb6b": "Airista Flow, Inc.", + "c4cb76": "Microsoft Corporation", "c4cbe1": "Dell Inc.", "c4cd45": "Beijing Boomsense Technology CO.,LTD.", "c4cd82": "Hangzhou Lowan Information Technology Co., Ltd.", @@ -29579,6 +29639,7 @@ "c8a1ba": "Neul Ltd", "c8a2ce": "Oasis Media Systems LLC", "c8a362": "ASIX Electronics Corporation", + "c8a3e8": "CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD.", "c8a40d": "Cooler Master Technology Inc", "c8a608": "Ruckus Wireless", "c8a620": "Nebula, Inc", @@ -29663,6 +29724,7 @@ "c8df84": "Texas Instruments", "c8e0eb": "Apple, Inc.", "c8e130": "Milkyway Group Ltd", + "c8e193": "ITEL MOBILE LIMITED", "c8e1a7": "Vertu Corporation Limited", "c8e265": "Intel Corporate", "c8e306": "eero inc.", @@ -29738,6 +29800,7 @@ "cc19a8": "PT Inovação e Sistemas SA", "cc1aa3": "Arista Networks", "cc1afa": "zte corporation", + "cc1b5a": "Ruckus Wireless", "cc1be0": "IEEE Registration Authority", "cc1e97": "HUAWEI TECHNOLOGIES CO.,LTD", "cc1eff": "Metrological Group BV", @@ -30055,6 +30118,7 @@ "d007ca": "Juniper Networks", "d009c8": "Cisco Systems, Inc", "d00aab": "Yokogawa Digital Computer Corporation", + "d00b27": "Murata Manufacturing Co., Ltd.", "d00df7": "Huawei Device Co., Ltd.", "d00ea4": "Porsche Cars North America", "d00ed9": "Taicang T&W Electronics", @@ -30528,6 +30592,7 @@ "d47c44": "IEEE Registration Authority", "d47dfc": "TECNO MOBILE LIMITED", "d47ee4": "China Mobile IOT Company Limited", + "d47f78": "Dopple B.V.", "d481ca": "iDevices, LLC", "d481d7": "Dell Inc.", "d4823e": "Argosy Technologies, Ltd.", @@ -30583,6 +30648,7 @@ "d49fdd": "Huawei Device Co., Ltd.", "d4a02a": "Cisco Systems, Inc", "d4a148": "HUAWEI TECHNOLOGIES CO.,LTD", + "d4a23d": "New H3C Technologies Co., Ltd", "d4a33d": "Apple, Inc.", "d4a38b": "ELE(GROUP)CO.,LTD", "d4a3eb": "Shenzhen iComm Semiconductor CO.,LTD", @@ -30705,6 +30771,7 @@ "d80093": "Aurender Inc.", "d8028a": "Shenzhen YOUHUA Technology Co., Ltd", "d8031a": "Laird Connectivity", + "d8032a": "COMMTACT LTD", "d8052e": "Skyviia Corporation", "d806d1": "Co,. Ltd.", "d807b6": "TP-LINK TECHNOLOGIES CO.,LTD.", @@ -30727,6 +30794,7 @@ "d8109f": "HUAWEI TECHNOLOGIES CO.,LTD", "d810cb": "Andrea Informatique", "d81265": "CHONGQING FUGUI ELECTRONICS CO.,LTD.", + "d8132a": "Espressif Inc.", "d81399": "Hui Zhou Gaoshengda Technology Co.,LTD", "d814d6": "SURE SYSTEM Co Ltd", "d814df": "Co., Ltd", @@ -30994,6 +31062,7 @@ "d8d45d": "Orbic North America", "d8d4e6": "Hytec Inter Co., Ltd.", "d8d5b9": "Rainforest Automation, Inc.", + "d8d668": "Tuya Smart Inc.", "d8d67e": "GSK CNC EQUIPMENT CO.,LTD", "d8d6f3": "Sdn. Bhd.", "d8d723": "IDS, Inc", @@ -31339,6 +31408,7 @@ "dce533": "IEEE Registration Authority", "dce55b": "Google, Inc.", "dce578": "Experimental Factory of Scientific Engineering and Special Design Department", + "dce5d8": "zte corporation", "dce650": "Extreme Networks Headquarters", "dce71c": "AUG Elektronik GmbH", "dce838": "Limited", @@ -31875,6 +31945,7 @@ "e482cc": "Jumptronic GmbH", "e48326": "HUAWEI TECHNOLOGIES CO.,LTD", "e48399": "ARRIS Group, Inc.", + "e48429": "New H3C Technologies Co., Ltd", "e4842b": "HANGZHOU SOFTEL OPTIC CO., LTD", "e484d3": "Xiaomi Communications Co Ltd", "e48501": "Geberit International AG", @@ -32617,6 +32688,7 @@ "eccd6d": "Allied Telesis, Inc.", "ecce13": "Cisco Systems, Inc", "ecced7": "Apple, Inc.", + "eccf70": "FUJIAN STAR-NET COMMUNICATION CO.,LTD", "ecd00e": "MiraeRecognition Co., Ltd.", "ecd040": "GEA Farm Technologies GmbH", "ecd09f": "Xiaomi Communications Co Ltd", @@ -32737,6 +32809,7 @@ "f03404": "TCT mobile ltd", "f03575": "Hui Zhou Gaoshengda Technology Co.,LTD", "f037a1": "CO., LTD.", + "f037cf": "Huawei Device Co., Ltd.", "f03965": "Samsung Electronics Co.,Ltd", "f03a4b": "Bloombase, Inc.", "f03a55": "Omega Elektronik AS", @@ -32991,6 +33064,7 @@ "f0fac7": "Huawei Device Co., Ltd.", "f0fcc8": "ARRIS Group, Inc.", "f0fda0": "Acurix Networks Pty Ltd", + "f0fddd": "Foxtron Vehicle Technologies Co., Ltd.", "f0fe6b": "Shanghai High-Flying Electronics Technology Co., Ltd", "f0fee7": "Huawei Device Co., Ltd.", "f40046": "ON Semiconductor", @@ -33026,6 +33100,7 @@ "f415fd": "Shanghai Pateo Electronic Equipment Manufacturing Co., Ltd.", "f417b8": "AirTies Wireless Networks", "f419e2": "Volterra", + "f41a79": "IEEE Registration Authority", "f41a9c": "Xiaomi Communications Co Ltd", "f41ab0": "Shenzhen Xingguodu Technology Co., Ltd.", "f41ba1": "Apple, Inc.", @@ -33280,6 +33355,7 @@ "f4dca5": "DAWON DNS", "f4dcda": "Zhuhai Jiahe Communication Technology Co., limited", "f4dcf9": "HUAWEI TECHNOLOGIES CO.,LTD", + "f4dd06": "Samsung Electronics Co.,Ltd", "f4dd9e": "GoPro", "f4de0c": "ESPOD Ltd.", "f4deaf": "HUAWEI TECHNOLOGIES CO.,LTD", @@ -33885,6 +33961,7 @@ "fc8a3d": "zte corporation", "fc8b97": "SHENZHEN GONGJIN ELECTRONICS CO.,LT", "fc8c11": "Microsoft Corporation", + "fc8d13": "FUJIAN STAR-NET COMMUNICATION CO.,LTD", "fc8d3d": "Leapfive Tech. Ltd.", "fc8e5b": "China Mobile Iot Limited company", "fc8e6e": "StreamCCTV, LLC", @@ -33965,6 +34042,7 @@ "fcbcd1": "HUAWEI TECHNOLOGIES CO.,LTD", "fcbd67": "Arista Networks", "fcbe7b": "vivo Mobile Communication Co., Ltd.", + "fcc0cc": "Yunke China Information Technology Limited", "fcc233": "ASUSTek COMPUTER INC.", "fcc23d": "Atmel Corporation", "fcc2de": "Murata Manufacturing Co., Ltd.", From 48c597cb49c4b90e4f5d3be7e86a002096d65ca1 Mon Sep 17 00:00:00 2001 From: gt732 <83186740+gt732@users.noreply.github.com> Date: Thu, 7 Sep 2023 14:39:27 -0400 Subject: [PATCH 16/21] Ubiquiti airFiber Parser (#329) * Revised Ubiquiti AirFiber parser * Changed parser name to device OS instead * Corrected parser docstrings to airOS * updated parser to remove # lines * Fixed black issue --- docs/dev/include_parser_list.md | 3 +- netutils/config/compliance.py | 1 + netutils/config/parser.py | 29 +++ .../ubiquiti_airos/airos_backup.txt | 229 ++++++++++++++++++ .../ubiquiti_airos/airos_feature.py | 5 + .../ubiquiti_airos/airos_intended.txt | 229 ++++++++++++++++++ .../ubiquiti_airos/airos_received.json | 32 +++ .../ubiquiti_airos/airos_full_received.py | 192 +++++++++++++++ .../base/ubiquiti_airos/airos_full_sent.txt | 229 ++++++++++++++++++ 9 files changed, 948 insertions(+), 1 deletion(-) create mode 100644 tests/unit/mock/config/compliance/compliance/ubiquiti_airos/airos_backup.txt create mode 100644 tests/unit/mock/config/compliance/compliance/ubiquiti_airos/airos_feature.py create mode 100644 tests/unit/mock/config/compliance/compliance/ubiquiti_airos/airos_intended.txt create mode 100644 tests/unit/mock/config/compliance/compliance/ubiquiti_airos/airos_received.json create mode 100644 tests/unit/mock/config/parser/base/ubiquiti_airos/airos_full_received.py create mode 100644 tests/unit/mock/config/parser/base/ubiquiti_airos/airos_full_sent.txt diff --git a/docs/dev/include_parser_list.md b/docs/dev/include_parser_list.md index 3c025637..0d70a62c 100644 --- a/docs/dev/include_parser_list.md +++ b/docs/dev/include_parser_list.md @@ -18,4 +18,5 @@ | netscaler | netutils.config.parser.NetscalerConfigParser | | nokia_sros | netutils.config.parser.NokiaConfigParser | | paloalto_panos | netutils.config.parser.PaloAltoNetworksConfigParser | -| ruckus_fastiron | netutils.config.parser.FastironConfigParser | \ No newline at end of file +| ruckus_fastiron | netutils.config.parser.FastironConfigParser | +| ubiquiti_airos | netutils.config.parser.UbiquitiAirOSConfigParser | \ No newline at end of file diff --git a/netutils/config/compliance.py b/netutils/config/compliance.py index fc1370ed..76f25771 100644 --- a/netutils/config/compliance.py +++ b/netutils/config/compliance.py @@ -26,6 +26,7 @@ "paloalto_panos": parser.PaloAltoNetworksConfigParser, "mikrotik_routeros": parser.RouterOSConfigParser, "ruckus_fastiron": parser.FastironConfigParser, + "ubiquiti_airos": parser.UbiquitiAirOSConfigParser, } diff --git a/netutils/config/parser.py b/netutils/config/parser.py index 90c3e139..2bcf2887 100644 --- a/netutils/config/parser.py +++ b/netutils/config/parser.py @@ -1575,3 +1575,32 @@ def _build_banner(self, config_line: str) -> t.Optional[str]: except StopIteration: return None raise ValueError("Unable to parse banner end.") + + +class UbiquitiAirOSConfigParser(BaseSpaceConfigParser): + """Ubiquiti airOS config parser.""" + + comment_chars: t.List[str] = ["#", "###"] + banner_start: t.List[str] = [] + + @property + def banner_end(self) -> str: + """Demarcate End of Banner char(s).""" + raise NotImplementedError("Ubiquiti airOS platform doesn't have a banner.") + + @property + def config_lines_only(self) -> str: + """Remove spaces and unwanted lines from config lines. + + Returns: + The non-space and non-comment lines from ``config``. + """ + config_lines = [] + config = self.config.strip() + for line in config.splitlines(): + if line.startswith("##A"): + config_lines.append(line) + if line and line != "##" and not self.is_comment(line): + config_lines.append(line) + + return "\n".join(config_lines) diff --git a/tests/unit/mock/config/compliance/compliance/ubiquiti_airos/airos_backup.txt b/tests/unit/mock/config/compliance/compliance/ubiquiti_airos/airos_backup.txt new file mode 100644 index 00000000..37b74b82 --- /dev/null +++ b/tests/unit/mock/config/compliance/compliance/ubiquiti_airos/airos_backup.txt @@ -0,0 +1,229 @@ +### PART ### +## +##AF.v4.1.0 +#board.sysid=0xaf01 +#board.cpurevision=0x4e840102 +#board.name=airFiber 24G +#board.shortname=AF24 +#board.subtype= +#board.netmodes=16; +#board.hwaddr=802AA8CF8DB6 +#board.reboot=130 +#board.upgrade=330 +#board.phycount=1 +#board.phy.1.maxmtu=1500 +#board.gps=1 +#board.airfiber=1 +#board.portnumber=1 +#board.fdd=1 +#feature.gps=1 +#feature.rssi.leds=0 +#feature.gps.leds=0 +#radio.1.name= +#radio.1.shortname= +#radio.1.bus=spi +#radio.1.rf_revision=0x0000 +#radio.1.subsystemid=0xaf01 +#radio.1.subvendorid=0x0777 +#radio.1.txpower.max=43 +#radio.1.txpower.offset=0 +#radio.1.antennas=1 +#radio.1.def_antenna=4 +#radio.1.antenna.1.id=4 +#radio.1.antenna.1.name=Combined +#radio.1.antenna.1.gain=0 +#radio.1.ccode=840 +#radio.1.ccode.fixed=0 +#radio.1.ccode.locked=1 +#radio.1.ptp_only=1 +#radio.1.ptp_sta=1 +#radio.1.chanbw=100 +#deac9d0212791db5ede667c016876ba4 +### PART ### +afnetconf.1.atpctarget=0 +afnetconf.1.autoneg=enabled +afnetconf.1.carrierdrop=off +afnetconf.1.carrierdropdebounce=0 +afnetconf.1.carrierdropto=5 +afnetconf.1.cdblockafterpulse=disabled +afnetconf.1.cdfirstsessiontimer=0 +afnetconf.1.cdfirstsessionwait=disabled +afnetconf.1.cdtracknosession=disabled +afnetconf.1.devname=af0 +afnetconf.1.flowcntl=disabled +afnetconf.1.mcastfilter=disabled +afnetconf.1.mincapholdoff=0 +afnetconf.1.mincapholdoffup=0 +afnetconf.1.minrxcapacity=0 +afnetconf.1.minrxcapacityup=0 +afnetconf.1.mintxcapacity=0 +afnetconf.1.mintxcapacityup=0 +afnetconf.1.status=enabled +afnetconf.status=enabled +airview.tcp_port=18888 +airview.tcpport=18888 +bridge.1.devname=br0 +bridge.1.fd=1 +bridge.1.port.1.devname=eth0 +bridge.1.port.1.status=enabled +bridge.1.port.2.devname=air0 +bridge.1.port.2.status=enabled +bridge.1.status=enabled +bridge.1.stp.status=disabled +bridge.status=enabled +dhcpc.1.devname=eth0 +dhcpc.1.status=disabled +dhcpc.2.devname=air0 +dhcpc.2.status=disabled +dhcpc.3.devname=br0 +dhcpc.3.status=disabled +dhcpc.status=disabled +discovery.cdp.status=disabled +discovery.status=enabled +dyndns.status=disabled +ebtables.status=enabled +ebtables.sys.arpnat.status=disabled +ebtables.sys.eap.status=disabled +ebtables.sys.status=enabled +gui.language=en_US +httpd.https.port=443 +httpd.https.status=disabled +httpd.port=80 +httpd.session.timeout=300 +httpd.status=enabled +netconf.1.allmulti=disabled +netconf.1.autoip.status=disabled +netconf.1.autoneg=enabled +netconf.1.devname=eth0 +netconf.1.hwaddr.mac= +netconf.1.hwaddr.status=disabled +netconf.1.ip=0.0.0.0 +netconf.1.mtu=1500 +netconf.1.netmask=255.255.255.0 +netconf.1.promisc=enabled +netconf.1.role=bridge_port +netconf.1.status=enabled +netconf.1.up=enabled +netconf.2.allmulti=disabled +netconf.2.autoip.status=disabled +netconf.2.devname=air0 +netconf.2.hwaddr.mac= +netconf.2.hwaddr.status=disabled +netconf.2.ip=0.0.0.0 +netconf.2.mtu=1500 +netconf.2.netmask=255.255.255.0 +netconf.2.promisc=enabled +netconf.2.role=bridge_port +netconf.2.status=enabled +netconf.2.up=enabled +netconf.3.allmulti=disabled +netconf.3.autoip.status=disabled +netconf.3.devname=br0 +netconf.3.hwaddr.mac= +netconf.3.hwaddr.status=disabled +netconf.3.ip=10.10.10.10 +netconf.3.mtu=1500 +netconf.3.netmask=255.255.255.0 +netconf.3.role=mlan +netconf.3.status=enabled +netconf.3.up=enabled +netconf.status=enabled +netmode=airfiber +ntpclient.status=disabled +ppp.status=disabled +pwdog.delay=300 +pwdog.host= +pwdog.period=300 +pwdog.retry=3 +pwdog.status=disabled +radio.1.antenna.gain=33 +radio.1.atpctarget=0 +radio.1.cable.loss=0 +radio.1.countrycode=840 +radio.1.dfsthreshold=0 +radio.1.dualfreqmode=enabled +radio.1.duplex=half +radio.1.dutycycle=50 +radio.1.framelength=-1 +radio.1.gps_sync=disabled +radio.1.key=s:password1 +radio.1.linkname=UBNT +radio.1.mode=slave +radio.1.pwrbackoff=disabled +radio.1.pwrout.10x=0 +radio.1.pwrout.4x=43 +radio.1.pwrout.6x=43 +radio.1.pwrout.8x=33 +radio.1.rate.auto=enabled +radio.1.rate=6x +radio.1.rfrate=6x +radio.1.rx_freq10=null +radio.1.rx_freq2=null +radio.1.rx_freq3=null +radio.1.rx_freq4=null +radio.1.rx_freq5=null +radio.1.rx_freq6=null +radio.1.rx_freq7=null +radio.1.rx_freq8=null +radio.1.rx_freq9=null +radio.1.rx_freq=24.1GHz +radio.1.rxchanbw=2048 +radio.1.rxgain=low +radio.1.sharedant=disabled +radio.1.sisomode=disabled +radio.1.status=enabled +radio.1.streammode=disabled +radio.1.subsystemid=0xaf01 +radio.1.tx_freq2=null +radio.1.tx_freq3=null +radio.1.tx_freq=24.1GHz +radio.1.txchanbw=2048 +radio.1.txpower=33 +radio.countrycode=840 +radio.status=enabled +resolv.host.1.name=UBNT +resolv.host.1.status=enabled +resolv.nameserver.1.ip=1.1.1.1 +resolv.nameserver.1.status=enabled +resolv.nameserver.2.ip= +resolv.nameserver.2.status=disabled +resolv.nameserver.status=enabled +resolv.status=disabled +route.1.comment= +route.1.devname=br0 +route.1.gateway=10.10.10.1 +route.1.ip=0.0.0.0 +route.1.netmask=0 +route.1.status=enabled +route.status=enabled +snmp.community=admin +snmp.contact=lab +snmp.location=lab +snmp.status=enabled +sshd.auth.passwd=enabled +sshd.port=22 +sshd.status=enabled +syslog.remote.port=514 +syslog.remote.status=disabled +syslog.status=disabled +system.button.reset=enabled +system.cfg.version=65540 +system.date.status=disabled +system.date.timestamp= +system.eirp.status=enabled +system.latitude= +system.longitude= +system.timezone=GMT +telnetd.port=23 +telnetd.status=enabled +ucode.1.rxscramblekey=0 +ucode.1.txscramblekey=0 +udapi_bridge.callhome.status=disabled +unms.uri=- +update.check.status=enabled +users.1.name=ubnt +users.1.password=$2$GXWw8TsR$eBl/.jP1ibig7GUV4nn65. +users.1.status=enabled +users.2.status=disabled +users.status=enabled +vlan.status=disabled \ No newline at end of file diff --git a/tests/unit/mock/config/compliance/compliance/ubiquiti_airos/airos_feature.py b/tests/unit/mock/config/compliance/compliance/ubiquiti_airos/airos_feature.py new file mode 100644 index 00000000..9cac2e12 --- /dev/null +++ b/tests/unit/mock/config/compliance/compliance/ubiquiti_airos/airos_feature.py @@ -0,0 +1,5 @@ +features = [ + {"name": "radio_country_code", "ordered": True, "section": ["radio.1.countrycode"]}, + {"name": "snmp", "ordered": True, "section": ["snmp.community"]}, + {"name": "radio_mode", "ordered": True, "section": ["radio.1.mode"]}, +] diff --git a/tests/unit/mock/config/compliance/compliance/ubiquiti_airos/airos_intended.txt b/tests/unit/mock/config/compliance/compliance/ubiquiti_airos/airos_intended.txt new file mode 100644 index 00000000..fd934b8b --- /dev/null +++ b/tests/unit/mock/config/compliance/compliance/ubiquiti_airos/airos_intended.txt @@ -0,0 +1,229 @@ +### PART ### +## +##AF.v4.1.0 +#board.sysid=0xaf01 +#board.cpurevision=0x4e840102 +#board.name=airFiber 24G +#board.shortname=AF24 +#board.subtype= +#board.netmodes=16; +#board.hwaddr=802AA8CF8DB6 +#board.reboot=130 +#board.upgrade=330 +#board.phycount=1 +#board.phy.1.maxmtu=1500 +#board.gps=1 +#board.airfiber=1 +#board.portnumber=1 +#board.fdd=1 +#feature.gps=1 +#feature.rssi.leds=0 +#feature.gps.leds=0 +#radio.1.name= +#radio.1.shortname= +#radio.1.bus=spi +#radio.1.rf_revision=0x0000 +#radio.1.subsystemid=0xaf01 +#radio.1.subvendorid=0x0777 +#radio.1.txpower.max=43 +#radio.1.txpower.offset=0 +#radio.1.antennas=1 +#radio.1.def_antenna=4 +#radio.1.antenna.1.id=4 +#radio.1.antenna.1.name=Combined +#radio.1.antenna.1.gain=0 +#radio.1.ccode=840 +#radio.1.ccode.fixed=0 +#radio.1.ccode.locked=1 +#radio.1.ptp_only=1 +#radio.1.ptp_sta=1 +#radio.1.chanbw=100 +#deac9d0212791db5ede667c016876ba4 +### PART ### +afnetconf.1.atpctarget=0 +afnetconf.1.autoneg=enabled +afnetconf.1.carrierdrop=off +afnetconf.1.carrierdropdebounce=0 +afnetconf.1.carrierdropto=5 +afnetconf.1.cdblockafterpulse=disabled +afnetconf.1.cdfirstsessiontimer=0 +afnetconf.1.cdfirstsessionwait=disabled +afnetconf.1.cdtracknosession=disabled +afnetconf.1.devname=af0 +afnetconf.1.flowcntl=disabled +afnetconf.1.mcastfilter=disabled +afnetconf.1.mincapholdoff=0 +afnetconf.1.mincapholdoffup=0 +afnetconf.1.minrxcapacity=0 +afnetconf.1.minrxcapacityup=0 +afnetconf.1.mintxcapacity=0 +afnetconf.1.mintxcapacityup=0 +afnetconf.1.status=enabled +afnetconf.status=enabled +airview.tcp_port=18888 +airview.tcpport=18888 +bridge.1.devname=br0 +bridge.1.fd=1 +bridge.1.port.1.devname=eth0 +bridge.1.port.1.status=enabled +bridge.1.port.2.devname=air0 +bridge.1.port.2.status=enabled +bridge.1.status=enabled +bridge.1.stp.status=disabled +bridge.status=enabled +dhcpc.1.devname=eth0 +dhcpc.1.status=disabled +dhcpc.2.devname=air0 +dhcpc.2.status=disabled +dhcpc.3.devname=br0 +dhcpc.3.status=disabled +dhcpc.status=disabled +discovery.cdp.status=disabled +discovery.status=enabled +dyndns.status=disabled +ebtables.status=enabled +ebtables.sys.arpnat.status=disabled +ebtables.sys.eap.status=disabled +ebtables.sys.status=enabled +gui.language=en_US +httpd.https.port=443 +httpd.https.status=disabled +httpd.port=80 +httpd.session.timeout=300 +httpd.status=enabled +netconf.1.allmulti=disabled +netconf.1.autoip.status=disabled +netconf.1.autoneg=enabled +netconf.1.devname=eth0 +netconf.1.hwaddr.mac= +netconf.1.hwaddr.status=disabled +netconf.1.ip=0.0.0.0 +netconf.1.mtu=1500 +netconf.1.netmask=255.255.255.0 +netconf.1.promisc=enabled +netconf.1.role=bridge_port +netconf.1.status=enabled +netconf.1.up=enabled +netconf.2.allmulti=disabled +netconf.2.autoip.status=disabled +netconf.2.devname=air0 +netconf.2.hwaddr.mac= +netconf.2.hwaddr.status=disabled +netconf.2.ip=0.0.0.0 +netconf.2.mtu=1500 +netconf.2.netmask=255.255.255.0 +netconf.2.promisc=enabled +netconf.2.role=bridge_port +netconf.2.status=enabled +netconf.2.up=enabled +netconf.3.allmulti=disabled +netconf.3.autoip.status=disabled +netconf.3.devname=br0 +netconf.3.hwaddr.mac= +netconf.3.hwaddr.status=disabled +netconf.3.ip=10.10.10.10 +netconf.3.mtu=1500 +netconf.3.netmask=255.255.255.0 +netconf.3.role=mlan +netconf.3.status=enabled +netconf.3.up=enabled +netconf.status=enabled +netmode=airfiber +ntpclient.status=disabled +ppp.status=disabled +pwdog.delay=300 +pwdog.host= +pwdog.period=300 +pwdog.retry=3 +pwdog.status=disabled +radio.1.antenna.gain=33 +radio.1.atpctarget=0 +radio.1.cable.loss=0 +radio.1.countrycode=840 +radio.1.dfsthreshold=0 +radio.1.dualfreqmode=enabled +radio.1.duplex=half +radio.1.dutycycle=50 +radio.1.framelength=-1 +radio.1.gps_sync=disabled +radio.1.key=s:password1 +radio.1.linkname=UBNT +radio.1.mode=master +radio.1.pwrbackoff=disabled +radio.1.pwrout.10x=0 +radio.1.pwrout.4x=43 +radio.1.pwrout.6x=43 +radio.1.pwrout.8x=33 +radio.1.rate.auto=enabled +radio.1.rate=6x +radio.1.rfrate=6x +radio.1.rx_freq10=null +radio.1.rx_freq2=null +radio.1.rx_freq3=null +radio.1.rx_freq4=null +radio.1.rx_freq5=null +radio.1.rx_freq6=null +radio.1.rx_freq7=null +radio.1.rx_freq8=null +radio.1.rx_freq9=null +radio.1.rx_freq=24.1GHz +radio.1.rxchanbw=2048 +radio.1.rxgain=low +radio.1.sharedant=disabled +radio.1.sisomode=disabled +radio.1.status=enabled +radio.1.streammode=disabled +radio.1.subsystemid=0xaf01 +radio.1.tx_freq2=null +radio.1.tx_freq3=null +radio.1.tx_freq=24.1GHz +radio.1.txchanbw=2048 +radio.1.txpower=33 +radio.countrycode=840 +radio.status=enabled +resolv.host.1.name=UBNT +resolv.host.1.status=enabled +resolv.nameserver.1.ip=1.1.1.1 +resolv.nameserver.1.status=enabled +resolv.nameserver.2.ip= +resolv.nameserver.2.status=disabled +resolv.nameserver.status=enabled +resolv.status=disabled +route.1.comment= +route.1.devname=br0 +route.1.gateway=10.10.10.1 +route.1.ip=0.0.0.0 +route.1.netmask=0 +route.1.status=enabled +route.status=enabled +snmp.community=admin +snmp.contact=lab +snmp.location=lab +snmp.status=enabled +sshd.auth.passwd=enabled +sshd.port=22 +sshd.status=enabled +syslog.remote.port=514 +syslog.remote.status=disabled +syslog.status=disabled +system.button.reset=enabled +system.cfg.version=65540 +system.date.status=disabled +system.date.timestamp= +system.eirp.status=enabled +system.latitude= +system.longitude= +system.timezone=GMT +telnetd.port=23 +telnetd.status=enabled +ucode.1.rxscramblekey=0 +ucode.1.txscramblekey=0 +udapi_bridge.callhome.status=disabled +unms.uri=- +update.check.status=enabled +users.1.name=ubnt +users.1.password=$2$GXWw8TsR$eBl/.jP1ibig7GUV4nn65. +users.1.status=enabled +users.2.status=disabled +users.status=enabled +vlan.status=disabled \ No newline at end of file diff --git a/tests/unit/mock/config/compliance/compliance/ubiquiti_airos/airos_received.json b/tests/unit/mock/config/compliance/compliance/ubiquiti_airos/airos_received.json new file mode 100644 index 00000000..14ebeeae --- /dev/null +++ b/tests/unit/mock/config/compliance/compliance/ubiquiti_airos/airos_received.json @@ -0,0 +1,32 @@ +{ + "radio_country_code": { + "compliant": true, + "missing": "", + "extra": "", + "cannot_parse": true, + "unordered_compliant": true, + "ordered_compliant": true, + "actual": "radio.1.countrycode=840", + "intended": "radio.1.countrycode=840" + }, + "snmp": { + "compliant": true, + "missing": "", + "extra": "", + "cannot_parse": true, + "unordered_compliant": true, + "ordered_compliant": true, + "actual": "snmp.community=admin", + "intended": "snmp.community=admin" + }, + "radio_mode": { + "compliant": false, + "missing": "radio.1.mode=master", + "extra": "radio.1.mode=slave", + "cannot_parse": true, + "unordered_compliant": false, + "ordered_compliant": false, + "actual": "radio.1.mode=slave", + "intended": "radio.1.mode=master" + } +} \ No newline at end of file diff --git a/tests/unit/mock/config/parser/base/ubiquiti_airos/airos_full_received.py b/tests/unit/mock/config/parser/base/ubiquiti_airos/airos_full_received.py new file mode 100644 index 00000000..fbe3e756 --- /dev/null +++ b/tests/unit/mock/config/parser/base/ubiquiti_airos/airos_full_received.py @@ -0,0 +1,192 @@ +from netutils.config.parser import ConfigLine + +data = [ + ConfigLine(config_line="##AF.v4.1.0", parents=()), + ConfigLine(config_line="afnetconf.1.atpctarget=0", parents=()), + ConfigLine(config_line="afnetconf.1.autoneg=enabled", parents=()), + ConfigLine(config_line="afnetconf.1.carrierdrop=off", parents=()), + ConfigLine(config_line="afnetconf.1.carrierdropdebounce=0", parents=()), + ConfigLine(config_line="afnetconf.1.carrierdropto=5", parents=()), + ConfigLine(config_line="afnetconf.1.cdblockafterpulse=disabled", parents=()), + ConfigLine(config_line="afnetconf.1.cdfirstsessiontimer=0", parents=()), + ConfigLine(config_line="afnetconf.1.cdfirstsessionwait=disabled", parents=()), + ConfigLine(config_line="afnetconf.1.cdtracknosession=disabled", parents=()), + ConfigLine(config_line="afnetconf.1.devname=af0", parents=()), + ConfigLine(config_line="afnetconf.1.flowcntl=disabled", parents=()), + ConfigLine(config_line="afnetconf.1.mcastfilter=disabled", parents=()), + ConfigLine(config_line="afnetconf.1.mincapholdoff=0", parents=()), + ConfigLine(config_line="afnetconf.1.mincapholdoffup=0", parents=()), + ConfigLine(config_line="afnetconf.1.minrxcapacity=0", parents=()), + ConfigLine(config_line="afnetconf.1.minrxcapacityup=0", parents=()), + ConfigLine(config_line="afnetconf.1.mintxcapacity=0", parents=()), + ConfigLine(config_line="afnetconf.1.mintxcapacityup=0", parents=()), + ConfigLine(config_line="afnetconf.1.status=enabled", parents=()), + ConfigLine(config_line="afnetconf.status=enabled", parents=()), + ConfigLine(config_line="airview.tcp_port=18888", parents=()), + ConfigLine(config_line="airview.tcpport=18888", parents=()), + ConfigLine(config_line="bridge.1.devname=br0", parents=()), + ConfigLine(config_line="bridge.1.fd=1", parents=()), + ConfigLine(config_line="bridge.1.port.1.devname=eth0", parents=()), + ConfigLine(config_line="bridge.1.port.1.status=enabled", parents=()), + ConfigLine(config_line="bridge.1.port.2.devname=air0", parents=()), + ConfigLine(config_line="bridge.1.port.2.status=enabled", parents=()), + ConfigLine(config_line="bridge.1.status=enabled", parents=()), + ConfigLine(config_line="bridge.1.stp.status=disabled", parents=()), + ConfigLine(config_line="bridge.status=enabled", parents=()), + ConfigLine(config_line="dhcpc.1.devname=eth0", parents=()), + ConfigLine(config_line="dhcpc.1.status=disabled", parents=()), + ConfigLine(config_line="dhcpc.2.devname=air0", parents=()), + ConfigLine(config_line="dhcpc.2.status=disabled", parents=()), + ConfigLine(config_line="dhcpc.3.devname=br0", parents=()), + ConfigLine(config_line="dhcpc.3.status=disabled", parents=()), + ConfigLine(config_line="dhcpc.status=disabled", parents=()), + ConfigLine(config_line="discovery.cdp.status=disabled", parents=()), + ConfigLine(config_line="discovery.status=enabled", parents=()), + ConfigLine(config_line="dyndns.status=disabled", parents=()), + ConfigLine(config_line="ebtables.status=enabled", parents=()), + ConfigLine(config_line="ebtables.sys.arpnat.status=disabled", parents=()), + ConfigLine(config_line="ebtables.sys.eap.status=disabled", parents=()), + ConfigLine(config_line="ebtables.sys.status=enabled", parents=()), + ConfigLine(config_line="gui.language=en_US", parents=()), + ConfigLine(config_line="httpd.https.port=443", parents=()), + ConfigLine(config_line="httpd.https.status=disabled", parents=()), + ConfigLine(config_line="httpd.port=80", parents=()), + ConfigLine(config_line="httpd.session.timeout=300", parents=()), + ConfigLine(config_line="httpd.status=enabled", parents=()), + ConfigLine(config_line="netconf.1.allmulti=disabled", parents=()), + ConfigLine(config_line="netconf.1.autoip.status=disabled", parents=()), + ConfigLine(config_line="netconf.1.autoneg=enabled", parents=()), + ConfigLine(config_line="netconf.1.devname=eth0", parents=()), + ConfigLine(config_line="netconf.1.hwaddr.mac=", parents=()), + ConfigLine(config_line="netconf.1.hwaddr.status=disabled", parents=()), + ConfigLine(config_line="netconf.1.ip=0.0.0.0", parents=()), + ConfigLine(config_line="netconf.1.mtu=1500", parents=()), + ConfigLine(config_line="netconf.1.netmask=255.255.255.0", parents=()), + ConfigLine(config_line="netconf.1.promisc=enabled", parents=()), + ConfigLine(config_line="netconf.1.role=bridge_port", parents=()), + ConfigLine(config_line="netconf.1.status=enabled", parents=()), + ConfigLine(config_line="netconf.1.up=enabled", parents=()), + ConfigLine(config_line="netconf.2.allmulti=disabled", parents=()), + ConfigLine(config_line="netconf.2.autoip.status=disabled", parents=()), + ConfigLine(config_line="netconf.2.devname=air0", parents=()), + ConfigLine(config_line="netconf.2.hwaddr.mac=", parents=()), + ConfigLine(config_line="netconf.2.hwaddr.status=disabled", parents=()), + ConfigLine(config_line="netconf.2.ip=0.0.0.0", parents=()), + ConfigLine(config_line="netconf.2.mtu=1500", parents=()), + ConfigLine(config_line="netconf.2.netmask=255.255.255.0", parents=()), + ConfigLine(config_line="netconf.2.promisc=enabled", parents=()), + ConfigLine(config_line="netconf.2.role=bridge_port", parents=()), + ConfigLine(config_line="netconf.2.status=enabled", parents=()), + ConfigLine(config_line="netconf.2.up=enabled", parents=()), + ConfigLine(config_line="netconf.3.allmulti=disabled", parents=()), + ConfigLine(config_line="netconf.3.autoip.status=disabled", parents=()), + ConfigLine(config_line="netconf.3.devname=br0", parents=()), + ConfigLine(config_line="netconf.3.hwaddr.mac=", parents=()), + ConfigLine(config_line="netconf.3.hwaddr.status=disabled", parents=()), + ConfigLine(config_line="netconf.3.ip=10.10.10.10", parents=()), + ConfigLine(config_line="netconf.3.mtu=1500", parents=()), + ConfigLine(config_line="netconf.3.netmask=255.255.255.0", parents=()), + ConfigLine(config_line="netconf.3.role=mlan", parents=()), + ConfigLine(config_line="netconf.3.status=enabled", parents=()), + ConfigLine(config_line="netconf.3.up=enabled", parents=()), + ConfigLine(config_line="netconf.status=enabled", parents=()), + ConfigLine(config_line="netmode=airfiber", parents=()), + ConfigLine(config_line="ntpclient.status=disabled", parents=()), + ConfigLine(config_line="ppp.status=disabled", parents=()), + ConfigLine(config_line="pwdog.delay=300", parents=()), + ConfigLine(config_line="pwdog.host=", parents=()), + ConfigLine(config_line="pwdog.period=300", parents=()), + ConfigLine(config_line="pwdog.retry=3", parents=()), + ConfigLine(config_line="pwdog.status=disabled", parents=()), + ConfigLine(config_line="radio.1.antenna.gain=33", parents=()), + ConfigLine(config_line="radio.1.atpctarget=0", parents=()), + ConfigLine(config_line="radio.1.cable.loss=0", parents=()), + ConfigLine(config_line="radio.1.countrycode=840", parents=()), + ConfigLine(config_line="radio.1.dfsthreshold=0", parents=()), + ConfigLine(config_line="radio.1.dualfreqmode=enabled", parents=()), + ConfigLine(config_line="radio.1.duplex=half", parents=()), + ConfigLine(config_line="radio.1.dutycycle=50", parents=()), + ConfigLine(config_line="radio.1.framelength=-1", parents=()), + ConfigLine(config_line="radio.1.gps_sync=disabled", parents=()), + ConfigLine(config_line="radio.1.key=s:password1", parents=()), + ConfigLine(config_line="radio.1.linkname=UBNT", parents=()), + ConfigLine(config_line="radio.1.mode=slave", parents=()), + ConfigLine(config_line="radio.1.pwrbackoff=disabled", parents=()), + ConfigLine(config_line="radio.1.pwrout.10x=0", parents=()), + ConfigLine(config_line="radio.1.pwrout.4x=43", parents=()), + ConfigLine(config_line="radio.1.pwrout.6x=43", parents=()), + ConfigLine(config_line="radio.1.pwrout.8x=33", parents=()), + ConfigLine(config_line="radio.1.rate.auto=enabled", parents=()), + ConfigLine(config_line="radio.1.rate=6x", parents=()), + ConfigLine(config_line="radio.1.rfrate=6x", parents=()), + ConfigLine(config_line="radio.1.rx_freq10=null", parents=()), + ConfigLine(config_line="radio.1.rx_freq2=null", parents=()), + ConfigLine(config_line="radio.1.rx_freq3=null", parents=()), + ConfigLine(config_line="radio.1.rx_freq4=null", parents=()), + ConfigLine(config_line="radio.1.rx_freq5=null", parents=()), + ConfigLine(config_line="radio.1.rx_freq6=null", parents=()), + ConfigLine(config_line="radio.1.rx_freq7=null", parents=()), + ConfigLine(config_line="radio.1.rx_freq8=null", parents=()), + ConfigLine(config_line="radio.1.rx_freq9=null", parents=()), + ConfigLine(config_line="radio.1.rx_freq=24.1GHz", parents=()), + ConfigLine(config_line="radio.1.rxchanbw=2048", parents=()), + ConfigLine(config_line="radio.1.rxgain=low", parents=()), + ConfigLine(config_line="radio.1.sharedant=disabled", parents=()), + ConfigLine(config_line="radio.1.sisomode=disabled", parents=()), + ConfigLine(config_line="radio.1.status=enabled", parents=()), + ConfigLine(config_line="radio.1.streammode=disabled", parents=()), + ConfigLine(config_line="radio.1.subsystemid=0xaf01", parents=()), + ConfigLine(config_line="radio.1.tx_freq2=null", parents=()), + ConfigLine(config_line="radio.1.tx_freq3=null", parents=()), + ConfigLine(config_line="radio.1.tx_freq=24.1GHz", parents=()), + ConfigLine(config_line="radio.1.txchanbw=2048", parents=()), + ConfigLine(config_line="radio.1.txpower=33", parents=()), + ConfigLine(config_line="radio.countrycode=840", parents=()), + ConfigLine(config_line="radio.status=enabled", parents=()), + ConfigLine(config_line="resolv.host.1.name=UBNT", parents=()), + ConfigLine(config_line="resolv.host.1.status=enabled", parents=()), + ConfigLine(config_line="resolv.nameserver.1.ip=1.1.1.1", parents=()), + ConfigLine(config_line="resolv.nameserver.1.status=enabled", parents=()), + ConfigLine(config_line="resolv.nameserver.2.ip=", parents=()), + ConfigLine(config_line="resolv.nameserver.2.status=disabled", parents=()), + ConfigLine(config_line="resolv.nameserver.status=enabled", parents=()), + ConfigLine(config_line="resolv.status=disabled", parents=()), + ConfigLine(config_line="route.1.comment=", parents=()), + ConfigLine(config_line="route.1.devname=br0", parents=()), + ConfigLine(config_line="route.1.gateway=10.10.10.1", parents=()), + ConfigLine(config_line="route.1.ip=0.0.0.0", parents=()), + ConfigLine(config_line="route.1.netmask=0", parents=()), + ConfigLine(config_line="route.1.status=enabled", parents=()), + ConfigLine(config_line="route.status=enabled", parents=()), + ConfigLine(config_line="snmp.community=admin", parents=()), + ConfigLine(config_line="snmp.contact=lab", parents=()), + ConfigLine(config_line="snmp.location=lab", parents=()), + ConfigLine(config_line="snmp.status=enabled", parents=()), + ConfigLine(config_line="sshd.auth.passwd=enabled", parents=()), + ConfigLine(config_line="sshd.port=22", parents=()), + ConfigLine(config_line="sshd.status=enabled", parents=()), + ConfigLine(config_line="syslog.remote.port=514", parents=()), + ConfigLine(config_line="syslog.remote.status=disabled", parents=()), + ConfigLine(config_line="syslog.status=disabled", parents=()), + ConfigLine(config_line="system.button.reset=enabled", parents=()), + ConfigLine(config_line="system.cfg.version=65540", parents=()), + ConfigLine(config_line="system.date.status=disabled", parents=()), + ConfigLine(config_line="system.date.timestamp=", parents=()), + ConfigLine(config_line="system.eirp.status=enabled", parents=()), + ConfigLine(config_line="system.latitude=", parents=()), + ConfigLine(config_line="system.longitude=", parents=()), + ConfigLine(config_line="system.timezone=GMT", parents=()), + ConfigLine(config_line="telnetd.port=23", parents=()), + ConfigLine(config_line="telnetd.status=enabled", parents=()), + ConfigLine(config_line="ucode.1.rxscramblekey=0", parents=()), + ConfigLine(config_line="ucode.1.txscramblekey=0", parents=()), + ConfigLine(config_line="udapi_bridge.callhome.status=disabled", parents=()), + ConfigLine(config_line="unms.uri=-", parents=()), + ConfigLine(config_line="update.check.status=enabled", parents=()), + ConfigLine(config_line="users.1.name=ubnt", parents=()), + ConfigLine(config_line="users.1.password=$1$YSWw8TsR$eBl/.jP1ibig8DUF4nn65.", parents=()), + ConfigLine(config_line="users.1.status=enabled", parents=()), + ConfigLine(config_line="users.2.status=disabled", parents=()), + ConfigLine(config_line="users.status=enabled", parents=()), + ConfigLine(config_line="vlan.status=disabled", parents=()), +] diff --git a/tests/unit/mock/config/parser/base/ubiquiti_airos/airos_full_sent.txt b/tests/unit/mock/config/parser/base/ubiquiti_airos/airos_full_sent.txt new file mode 100644 index 00000000..6178e9ce --- /dev/null +++ b/tests/unit/mock/config/parser/base/ubiquiti_airos/airos_full_sent.txt @@ -0,0 +1,229 @@ +### PART ### +## +##AF.v4.1.0 +#board.sysid=0xaf01 +#board.cpurevision=0x4e840102 +#board.name=airFiber 24G +#board.shortname=AF24 +#board.subtype= +#board.netmodes=16; +#board.hwaddr=802AA8CF8DB9 +#board.reboot=130 +#board.upgrade=330 +#board.phycount=1 +#board.phy.1.maxmtu=1500 +#board.gps=1 +#board.airfiber=1 +#board.portnumber=1 +#board.fdd=1 +#feature.gps=1 +#feature.rssi.leds=0 +#feature.gps.leds=0 +#radio.1.name= +#radio.1.shortname= +#radio.1.bus=spi +#radio.1.rf_revision=0x0000 +#radio.1.subsystemid=0xaf01 +#radio.1.subvendorid=0x0777 +#radio.1.txpower.max=43 +#radio.1.txpower.offset=0 +#radio.1.antennas=1 +#radio.1.def_antenna=4 +#radio.1.antenna.1.id=4 +#radio.1.antenna.1.name=Combined +#radio.1.antenna.1.gain=0 +#radio.1.ccode=840 +#radio.1.ccode.fixed=0 +#radio.1.ccode.locked=1 +#radio.1.ptp_only=1 +#radio.1.ptp_sta=1 +#radio.1.chanbw=100 +#deac9d0213801db5ede667c016886ba4 +### PART ### +afnetconf.1.atpctarget=0 +afnetconf.1.autoneg=enabled +afnetconf.1.carrierdrop=off +afnetconf.1.carrierdropdebounce=0 +afnetconf.1.carrierdropto=5 +afnetconf.1.cdblockafterpulse=disabled +afnetconf.1.cdfirstsessiontimer=0 +afnetconf.1.cdfirstsessionwait=disabled +afnetconf.1.cdtracknosession=disabled +afnetconf.1.devname=af0 +afnetconf.1.flowcntl=disabled +afnetconf.1.mcastfilter=disabled +afnetconf.1.mincapholdoff=0 +afnetconf.1.mincapholdoffup=0 +afnetconf.1.minrxcapacity=0 +afnetconf.1.minrxcapacityup=0 +afnetconf.1.mintxcapacity=0 +afnetconf.1.mintxcapacityup=0 +afnetconf.1.status=enabled +afnetconf.status=enabled +airview.tcp_port=18888 +airview.tcpport=18888 +bridge.1.devname=br0 +bridge.1.fd=1 +bridge.1.port.1.devname=eth0 +bridge.1.port.1.status=enabled +bridge.1.port.2.devname=air0 +bridge.1.port.2.status=enabled +bridge.1.status=enabled +bridge.1.stp.status=disabled +bridge.status=enabled +dhcpc.1.devname=eth0 +dhcpc.1.status=disabled +dhcpc.2.devname=air0 +dhcpc.2.status=disabled +dhcpc.3.devname=br0 +dhcpc.3.status=disabled +dhcpc.status=disabled +discovery.cdp.status=disabled +discovery.status=enabled +dyndns.status=disabled +ebtables.status=enabled +ebtables.sys.arpnat.status=disabled +ebtables.sys.eap.status=disabled +ebtables.sys.status=enabled +gui.language=en_US +httpd.https.port=443 +httpd.https.status=disabled +httpd.port=80 +httpd.session.timeout=300 +httpd.status=enabled +netconf.1.allmulti=disabled +netconf.1.autoip.status=disabled +netconf.1.autoneg=enabled +netconf.1.devname=eth0 +netconf.1.hwaddr.mac= +netconf.1.hwaddr.status=disabled +netconf.1.ip=0.0.0.0 +netconf.1.mtu=1500 +netconf.1.netmask=255.255.255.0 +netconf.1.promisc=enabled +netconf.1.role=bridge_port +netconf.1.status=enabled +netconf.1.up=enabled +netconf.2.allmulti=disabled +netconf.2.autoip.status=disabled +netconf.2.devname=air0 +netconf.2.hwaddr.mac= +netconf.2.hwaddr.status=disabled +netconf.2.ip=0.0.0.0 +netconf.2.mtu=1500 +netconf.2.netmask=255.255.255.0 +netconf.2.promisc=enabled +netconf.2.role=bridge_port +netconf.2.status=enabled +netconf.2.up=enabled +netconf.3.allmulti=disabled +netconf.3.autoip.status=disabled +netconf.3.devname=br0 +netconf.3.hwaddr.mac= +netconf.3.hwaddr.status=disabled +netconf.3.ip=10.10.10.10 +netconf.3.mtu=1500 +netconf.3.netmask=255.255.255.0 +netconf.3.role=mlan +netconf.3.status=enabled +netconf.3.up=enabled +netconf.status=enabled +netmode=airfiber +ntpclient.status=disabled +ppp.status=disabled +pwdog.delay=300 +pwdog.host= +pwdog.period=300 +pwdog.retry=3 +pwdog.status=disabled +radio.1.antenna.gain=33 +radio.1.atpctarget=0 +radio.1.cable.loss=0 +radio.1.countrycode=840 +radio.1.dfsthreshold=0 +radio.1.dualfreqmode=enabled +radio.1.duplex=half +radio.1.dutycycle=50 +radio.1.framelength=-1 +radio.1.gps_sync=disabled +radio.1.key=s:password1 +radio.1.linkname=UBNT +radio.1.mode=slave +radio.1.pwrbackoff=disabled +radio.1.pwrout.10x=0 +radio.1.pwrout.4x=43 +radio.1.pwrout.6x=43 +radio.1.pwrout.8x=33 +radio.1.rate.auto=enabled +radio.1.rate=6x +radio.1.rfrate=6x +radio.1.rx_freq10=null +radio.1.rx_freq2=null +radio.1.rx_freq3=null +radio.1.rx_freq4=null +radio.1.rx_freq5=null +radio.1.rx_freq6=null +radio.1.rx_freq7=null +radio.1.rx_freq8=null +radio.1.rx_freq9=null +radio.1.rx_freq=24.1GHz +radio.1.rxchanbw=2048 +radio.1.rxgain=low +radio.1.sharedant=disabled +radio.1.sisomode=disabled +radio.1.status=enabled +radio.1.streammode=disabled +radio.1.subsystemid=0xaf01 +radio.1.tx_freq2=null +radio.1.tx_freq3=null +radio.1.tx_freq=24.1GHz +radio.1.txchanbw=2048 +radio.1.txpower=33 +radio.countrycode=840 +radio.status=enabled +resolv.host.1.name=UBNT +resolv.host.1.status=enabled +resolv.nameserver.1.ip=1.1.1.1 +resolv.nameserver.1.status=enabled +resolv.nameserver.2.ip= +resolv.nameserver.2.status=disabled +resolv.nameserver.status=enabled +resolv.status=disabled +route.1.comment= +route.1.devname=br0 +route.1.gateway=10.10.10.1 +route.1.ip=0.0.0.0 +route.1.netmask=0 +route.1.status=enabled +route.status=enabled +snmp.community=admin +snmp.contact=lab +snmp.location=lab +snmp.status=enabled +sshd.auth.passwd=enabled +sshd.port=22 +sshd.status=enabled +syslog.remote.port=514 +syslog.remote.status=disabled +syslog.status=disabled +system.button.reset=enabled +system.cfg.version=65540 +system.date.status=disabled +system.date.timestamp= +system.eirp.status=enabled +system.latitude= +system.longitude= +system.timezone=GMT +telnetd.port=23 +telnetd.status=enabled +ucode.1.rxscramblekey=0 +ucode.1.txscramblekey=0 +udapi_bridge.callhome.status=disabled +unms.uri=- +update.check.status=enabled +users.1.name=ubnt +users.1.password=$1$YSWw8TsR$eBl/.jP1ibig8DUF4nn65. +users.1.status=enabled +users.2.status=disabled +users.status=enabled +vlan.status=disabled \ No newline at end of file From a276df8a6558e1f0069aa3e9f285e98a37e8f693 Mon Sep 17 00:00:00 2001 From: Ken Celenza Date: Thu, 7 Sep 2023 20:09:15 -0400 Subject: [PATCH 17/21] minor spelling issue (#365) --- docs/dev/dev_config.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev/dev_config.md b/docs/dev/dev_config.md index 86901d56..d68cabdf 100644 --- a/docs/dev/dev_config.md +++ b/docs/dev/dev_config.md @@ -32,7 +32,7 @@ The conversion from curly to set format via the `paloalto_panos_brace_to_set` fu ### Duplicate Line Detection -In some circumstances replacing lines, such as secrets without uniqueness in the replacement, will result in duplicated lines that are invalid configuration, such as:: +In some circumstances replacing lines, such as secrets without uniqueness in the replacement, will result in duplicated lines that are invalid configuration, such as: ```text snmp-server community <> RO SNMP_ACL_RO From 77b3ae2c20e193d340d20a45b057efdb8e4bae4c Mon Sep 17 00:00:00 2001 From: Ken Celenza Date: Thu, 7 Sep 2023 20:10:04 -0400 Subject: [PATCH 18/21] Fix issue where interface abbreviation not working and update mac docs (#346) --- netutils/interface.py | 2 +- netutils/mac.py | 9 +++++++-- tests/unit/test_interface.py | 9 +++++++++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/netutils/interface.py b/netutils/interface.py index ea18fe76..3466b271 100644 --- a/netutils/interface.py +++ b/netutils/interface.py @@ -89,7 +89,7 @@ def split_interface(interface: str) -> t.Tuple[str, str]: ('Eth', '1') >>> """ - head = interface.rstrip(r"/\0123456789. ") + head = interface.rstrip(r"/\0123456789.: ") tail = interface[len(head) :].lstrip() # noqa: E203 return (head, tail) diff --git a/netutils/mac.py b/netutils/mac.py index 1f29b86e..67f1b6d6 100644 --- a/netutils/mac.py +++ b/netutils/mac.py @@ -51,9 +51,13 @@ def is_valid_mac(mac: str) -> bool: def mac_to_format(mac: str, frmt: str = "MAC_NO_SPECIAL") -> str: """Converts the MAC address to a specific format. + The `frmt` is a combination delimiter (COLON, DASH, DOT) and number + of characters (TWO, FOUR), e.g. f'MAC_{delimiter}_{char_num}' or if no + special characters as `MAC_NO_SPECIAL`. + Args: mac: A MAC address in string format that matches one of the defined regex patterns. - frmt: A format in which the MAC address should be returned in. + frmt: A format in which the MAC address should be returned in, one of MAC_COLON_TWO, MAC_COLON_FOUR, MAC_DASH_TWO, MAC_DASH_FOUR, MAC_DOT_TWO, MAC_DOT_FOUR, MAC_NO_SPECIAL. Returns: A MAC address in the specified format. @@ -65,7 +69,8 @@ def mac_to_format(mac: str, frmt: str = "MAC_NO_SPECIAL") -> str: >>> """ if not MAC_CREATE.get(frmt): - raise ValueError(f"An invalid mac format was provided in: `{frmt}`") + format_choices = ", ".join(MAC_CREATE) + raise ValueError(f"An invalid mac format was provided in: `{frmt}`, not one of [{format_choices}]") mac = mac_normalize(mac) count = MAC_CREATE[frmt]["count"] char = MAC_CREATE[frmt]["char"] diff --git a/tests/unit/test_interface.py b/tests/unit/test_interface.py index 75e624c4..92292b67 100644 --- a/tests/unit/test_interface.py +++ b/tests/unit/test_interface.py @@ -10,6 +10,10 @@ "received": ("GigabitEthernet", "1/0/1"), }, {"sent": {"interface": "Gi1/0/1"}, "received": ("Gi", "1/0/1")}, + {"sent": {"interface": "Serial0/0/0:0"}, "received": ("Serial", "0/0/0:0")}, + {"sent": {"interface": "VLAN101"}, "received": ("VLAN", "101")}, + {"sent": {"interface": "Port-channel40"}, "received": ("Port-channel", "40")}, + {"sent": {"interface": "Gi1/0/3.100"}, "received": ("Gi", "1/0/3.100")}, ] CANONICAL_INTERFACE_NAME = [ @@ -158,6 +162,11 @@ "received": "SupE1/0/1", }, {"sent": {"interface": "Noninterface1/0/1"}, "received": "Noninterface1/0/1"}, + {"sent": {"interface": "Noninterface1/0/1"}, "received": "Noninterface1/0/1"}, + {"sent": {"interface": "Serial0/0/0:0"}, "received": "Se0/0/0:0"}, + {"sent": {"interface": "VLAN101"}, "received": "Vl101"}, + {"sent": {"interface": "Port-channel40"}, "received": "Po40"}, + {"sent": {"interface": "GigabitEthernet1/0/3.100"}, "received": "Gi1/0/3.100"}, ] INTERFACE_EXPANSION = [ From 42ad81d2aa5d075014723f5ed972b8c545fe0821 Mon Sep 17 00:00:00 2001 From: Ken Celenza Date: Thu, 7 Sep 2023 20:59:58 -0400 Subject: [PATCH 19/21] =?UTF-8?q?Add=20netutils=20parse=20to=20lib=20mappe?= =?UTF-8?q?r,=20alpha=20sort=20lib=20mapper,=20and=20clean=20up=E2=80=A6?= =?UTF-8?q?=20(#352)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- development_scripts.py | 48 +++-- docs/dev/dev_config.md | 3 + docs/user/lib_mapper/napalm.md | 1 + docs/user/lib_mapper/napalm_reverse.md | 1 + docs/user/lib_mapper/netmiko.md | 110 ++++++++++++ docs/user/lib_mapper/netmiko_reverse.md | 110 ++++++++++++ docs/user/lib_mapper/netutilsparser.md | 22 +++ .../user/lib_mapper/netutilsparser_reverse.md | 22 +++ docs/user/lib_use_cases_lib_mapper.md | 32 +++- netutils/config/compliance.py | 20 +-- netutils/lib_mapper.py | 168 ++++++++++++------ tests/unit/test_lib_mapper.py | 27 ++- 12 files changed, 475 insertions(+), 89 deletions(-) create mode 100644 docs/user/lib_mapper/netmiko.md create mode 100644 docs/user/lib_mapper/netmiko_reverse.md create mode 100644 docs/user/lib_mapper/netutilsparser.md create mode 100644 docs/user/lib_mapper/netutilsparser_reverse.md diff --git a/development_scripts.py b/development_scripts.py index 1ca53dab..3290f1ba 100644 --- a/development_scripts.py +++ b/development_scripts.py @@ -46,6 +46,18 @@ "_dict": lib_mapper.ANSIBLE_LIB_MAPPER_REVERSE, "_file": "docs/user/lib_mapper/ansible_reverse.md", }, + "hierconfig": { + "header_src": "HIERCONFIG", + "header_dst": "NORMALIZED", + "_dict": lib_mapper.HIERCONFIG_LIB_MAPPER, + "_file": "docs/user/lib_mapper/hierconfig.md", + }, + "hierconfig_reverse": { + "header_src": "NORMALIZED", + "header_dst": "HIERCONFIG", + "_dict": lib_mapper.HIERCONFIG_LIB_MAPPER_REVERSE, + "_file": "docs/user/lib_mapper/hierconfig_reverse.md", + }, "napalm": { "header_src": "NAPALM", "header_dst": "NORMALIZED", @@ -58,6 +70,30 @@ "_dict": lib_mapper.NAPALM_LIB_MAPPER_REVERSE, "_file": "docs/user/lib_mapper/napalm_reverse.md", }, + "netmiko": { + "header_src": "NETMIKO", + "header_dst": "NORMALIZED", + "_dict": lib_mapper.NETMIKO_LIB_MAPPER, + "_file": "docs/user/lib_mapper/netmiko.md", + }, + "netmiko_reverse": { + "header_src": "NORMALIZED", + "header_dst": "NETMIKO", + "_dict": lib_mapper.NETMIKO_LIB_MAPPER_REVERSE, + "_file": "docs/user/lib_mapper/netmiko_reverse.md", + }, + "netutilsparser": { + "header_src": "NETUTILSPARSER", + "header_dst": "NORMALIZED", + "_dict": lib_mapper.NETUTILSPARSER_LIB_MAPPER, + "_file": "docs/user/lib_mapper/netutilsparser.md", + }, + "netutilsparser_reverse": { + "header_src": "NORMALIZED", + "header_dst": "NETUTILSPARSER", + "_dict": lib_mapper.NETUTILSPARSER_LIB_MAPPER_REVERSE, + "_file": "docs/user/lib_mapper/netutilsparser_reverse.md", + }, "ntctemplates": { "header_src": "NTCTEMPLATES", "header_dst": "NORMALIZED", @@ -106,18 +142,6 @@ "_dict": lib_mapper.SCRAPLI_LIB_MAPPER_REVERSE, "_file": "docs/user/lib_mapper/scrapli_reverse.md", }, - "hierconfig": { - "header_src": "HIERCONFIG", - "header_dst": "NORMALIZED", - "_dict": lib_mapper.HIERCONFIG_LIB_MAPPER, - "_file": "docs/user/lib_mapper/hierconfig.md", - }, - "hierconfig_reverse": { - "header_src": "NORMALIZED", - "header_dst": "HIERCONFIG", - "_dict": lib_mapper.HIERCONFIG_LIB_MAPPER_REVERSE, - "_file": "docs/user/lib_mapper/hierconfig_reverse.md", - }, }, "PARSER_MAPPER_TEMPLATE": { "default": { diff --git a/docs/dev/dev_config.md b/docs/dev/dev_config.md index d68cabdf..c612d520 100644 --- a/docs/dev/dev_config.md +++ b/docs/dev/dev_config.md @@ -71,6 +71,9 @@ There are a series of considerations documented below, when developing a new par - The class must provide a `self.config_lines` that is a list of `ConfigLine` named tuples. - Build tests for the `tests/unit/mock/config/compliance/compliance/{os_name}/*` and `tests/unit/mock/config/parser/base/{os_name}/*`. - Add to `netutils/config/compliance.py` the `parser_map`, that maps the name of the parser to the Plugin. + - Ensure that the key name is based on `netutils.lib_mapper.MAIN_LIB_MAPPER_LIB_MAPPER` definitions. + - Ensure that it is alpha sorted. + - Ensure that it the key is added to `netutils.lib_mapper.NETUTILSPARSER_LIB_MAPPER` and `netutils.lib_mapper.NETUTILSPARSER_LIB_MAPPER_REVERSE`. - Fill out docstrings in the class and methods within the class that describe the parameters and an Example that compiles. - The following tips will generally be applicable. - Generally a class method should provide a `comment_chars` and `banner_start` as well as sometimes `banner_end`. diff --git a/docs/user/lib_mapper/napalm.md b/docs/user/lib_mapper/napalm.md index 9a352b26..9625bd41 100644 --- a/docs/user/lib_mapper/napalm.md +++ b/docs/user/lib_mapper/napalm.md @@ -4,6 +4,7 @@ | asa | → | cisco_asa | | cisco_wlc_ssh | → | cisco_wlc | | eos | → | arista_eos | +| f5 | → | bigip_f5 | | fortios | → | fortinet | | huawei | → | huawei_vrp | | ios | → | cisco_ios | diff --git a/docs/user/lib_mapper/napalm_reverse.md b/docs/user/lib_mapper/napalm_reverse.md index 1a2bad98..f929cd41 100644 --- a/docs/user/lib_mapper/napalm_reverse.md +++ b/docs/user/lib_mapper/napalm_reverse.md @@ -2,6 +2,7 @@ | ---------- | -- | ------ | | arista_eos | → | eos | | aruba_aoscx | → | aoscx | +| bigip_f5 | → | f5 | | brocade_vyos | → | vyos | | cisco_asa | → | asa | | cisco_ios | → | ios | diff --git a/docs/user/lib_mapper/netmiko.md b/docs/user/lib_mapper/netmiko.md new file mode 100644 index 00000000..36610fbf --- /dev/null +++ b/docs/user/lib_mapper/netmiko.md @@ -0,0 +1,110 @@ +| NETMIKO | | NORMALIZED | +| ---------- | -- | ------ | +| a10 | → | a10 | +| accedian | → | accedian | +| adtran_os | → | adtran_os | +| alcatel_aos | → | alcatel_aos | +| alcatel_sros | → | alcatel_sros | +| allied_telesis_awplus | → | allied_telesis_awplus | +| apresia_aeos | → | apresia_aeos | +| arista_eos | → | arista_eos | +| aruba_os | → | aruba_os | +| aruba_osswitch | → | aruba_osswitch | +| aruba_procurve | → | aruba_procurve | +| avaya_ers | → | avaya_ers | +| avaya_vsp | → | avaya_vsp | +| broadcom_icos | → | broadcom_icos | +| brocade_fastiron | → | brocade_fastiron | +| brocade_fos | → | brocade_fos | +| brocade_netiron | → | brocade_netiron | +| brocade_nos | → | brocade_nos | +| brocade_vdx | → | brocade_vdx | +| brocade_vyos | → | brocade_vyos | +| calix_b6 | → | calix_b6 | +| centec_os | → | centec_os | +| checkpoint_gaia | → | checkpoint_gaia | +| ciena_saos | → | ciena_saos | +| cisco_asa | → | cisco_asa | +| cisco_ftd | → | cisco_ftd | +| cisco_ios | → | cisco_ios | +| cisco_nxos | → | cisco_nxos | +| cisco_s300 | → | cisco_s300 | +| cisco_tp | → | cisco_tp | +| cisco_wlc | → | cisco_wlc | +| cisco_xe | → | cisco_xe | +| cisco_xr | → | cisco_xr | +| cloudgenix_ion | → | cloudgenix_ion | +| coriant | → | coriant | +| dell_dnos9 | → | dell_dnos9 | +| dell_force10 | → | dell_force10 | +| dell_isilon | → | dell_isilon | +| dell_os10 | → | dell_os10 | +| dell_os6 | → | dell_os6 | +| dell_os9 | → | dell_os9 | +| dell_powerconnect | → | dell_powerconnect | +| dlink_ds | → | dlink_ds | +| eltex | → | eltex | +| eltex_esr | → | eltex_esr | +| endace | → | endace | +| enterasys | → | enterasys | +| ericsson_ipos | → | ericsson_ipos | +| extreme | → | extreme | +| extreme_ers | → | extreme_ers | +| extreme_exos | → | extreme_exos | +| extreme_netiron | → | extreme_netiron | +| extreme_nos | → | extreme_nos | +| extreme_slx | → | extreme_slx | +| extreme_vdx | → | extreme_vdx | +| extreme_vsp | → | extreme_vsp | +| extreme_wing | → | extreme_wing | +| f5_linux | → | f5_linux | +| f5_ltm | → | f5_ltm | +| f5_tmsh | → | f5_tmsh | +| flexvnf | → | flexvnf | +| fortinet | → | fortinet | +| generic | → | generic | +| generic_termserver | → | generic_termserver | +| hp_comware | → | hp_comware | +| hp_procurve | → | hp_procurve | +| huawei | → | huawei | +| huawei_olt | → | huawei_olt | +| huawei_smartax | → | huawei_smartax | +| huawei_vrpv8 | → | huawei_vrpv8 | +| ipinfusion_ocnos | → | ipinfusion_ocnos | +| juniper | → | juniper | +| juniper_junos | → | juniper_junos | +| juniper_screenos | → | juniper_screenos | +| keymile | → | keymile | +| keymile_nos | → | keymile_nos | +| linux | → | linux | +| mellanox | → | mellanox | +| mellanox_mlnxos | → | mellanox_mlnxos | +| mikrotik_routeros | → | mikrotik_routeros | +| mikrotik_switchos | → | mikrotik_switchos | +| mrv_lx | → | mrv_lx | +| mrv_optiswitch | → | mrv_optiswitch | +| netapp_cdot | → | netapp_cdot | +| netgear_prosafe | → | netgear_prosafe | +| netscaler | → | netscaler | +| nokia_sros | → | nokia_sros | +| oneaccess_oneos | → | oneaccess_oneos | +| ovs_linux | → | ovs_linux | +| paloalto_panos | → | paloalto_panos | +| pluribus | → | pluribus | +| quanta_mesh | → | quanta_mesh | +| rad_etx | → | rad_etx | +| raisecom_roap | → | raisecom_roap | +| ruckus_fastiron | → | ruckus_fastiron | +| ruijie_os | → | ruijie_os | +| sixwind_os | → | sixwind_os | +| sophos_sfos | → | sophos_sfos | +| tplink_jetstream | → | tplink_jetstream | +| ubiquiti_edge | → | ubiquiti_edge | +| ubiquiti_edgerouter | → | ubiquiti_edgerouter | +| ubiquiti_edgeswitch | → | ubiquiti_edgeswitch | +| ubiquiti_unifiswitch | → | ubiquiti_unifiswitch | +| vyatta_vyos | → | vyatta_vyos | +| vyos | → | vyos | +| watchguard_fireware | → | watchguard_fireware | +| yamaha | → | yamaha | +| zte_zxros | → | zte_zxros | \ No newline at end of file diff --git a/docs/user/lib_mapper/netmiko_reverse.md b/docs/user/lib_mapper/netmiko_reverse.md new file mode 100644 index 00000000..9a9bd6c4 --- /dev/null +++ b/docs/user/lib_mapper/netmiko_reverse.md @@ -0,0 +1,110 @@ +| NORMALIZED | | NETMIKO | +| ---------- | -- | ------ | +| a10 | → | a10 | +| accedian | → | accedian | +| adtran_os | → | adtran_os | +| alcatel_aos | → | alcatel_aos | +| alcatel_sros | → | alcatel_sros | +| allied_telesis_awplus | → | allied_telesis_awplus | +| apresia_aeos | → | apresia_aeos | +| arista_eos | → | arista_eos | +| aruba_os | → | aruba_os | +| aruba_osswitch | → | aruba_osswitch | +| aruba_procurve | → | aruba_procurve | +| avaya_ers | → | avaya_ers | +| avaya_vsp | → | avaya_vsp | +| broadcom_icos | → | broadcom_icos | +| brocade_fastiron | → | brocade_fastiron | +| brocade_fos | → | brocade_fos | +| brocade_netiron | → | brocade_netiron | +| brocade_nos | → | brocade_nos | +| brocade_vdx | → | brocade_vdx | +| brocade_vyos | → | brocade_vyos | +| calix_b6 | → | calix_b6 | +| centec_os | → | centec_os | +| checkpoint_gaia | → | checkpoint_gaia | +| ciena_saos | → | ciena_saos | +| cisco_asa | → | cisco_asa | +| cisco_ftd | → | cisco_ftd | +| cisco_ios | → | cisco_ios | +| cisco_nxos | → | cisco_nxos | +| cisco_s300 | → | cisco_s300 | +| cisco_tp | → | cisco_tp | +| cisco_wlc | → | cisco_wlc | +| cisco_xe | → | cisco_xe | +| cisco_xr | → | cisco_xr | +| cloudgenix_ion | → | cloudgenix_ion | +| coriant | → | coriant | +| dell_dnos9 | → | dell_dnos9 | +| dell_force10 | → | dell_force10 | +| dell_isilon | → | dell_isilon | +| dell_os10 | → | dell_os10 | +| dell_os6 | → | dell_os6 | +| dell_os9 | → | dell_os9 | +| dell_powerconnect | → | dell_powerconnect | +| dlink_ds | → | dlink_ds | +| eltex | → | eltex | +| eltex_esr | → | eltex_esr | +| endace | → | endace | +| enterasys | → | enterasys | +| ericsson_ipos | → | ericsson_ipos | +| extreme | → | extreme | +| extreme_ers | → | extreme_ers | +| extreme_exos | → | extreme_exos | +| extreme_netiron | → | extreme_netiron | +| extreme_nos | → | extreme_nos | +| extreme_slx | → | extreme_slx | +| extreme_vdx | → | extreme_vdx | +| extreme_vsp | → | extreme_vsp | +| extreme_wing | → | extreme_wing | +| f5_linux | → | f5_linux | +| f5_ltm | → | f5_ltm | +| f5_tmsh | → | f5_tmsh | +| flexvnf | → | flexvnf | +| fortinet | → | fortinet | +| generic | → | generic | +| generic_termserver | → | generic_termserver | +| hp_comware | → | hp_comware | +| hp_procurve | → | hp_procurve | +| huawei | → | huawei | +| huawei_olt | → | huawei_olt | +| huawei_smartax | → | huawei_smartax | +| huawei_vrpv8 | → | huawei_vrpv8 | +| ipinfusion_ocnos | → | ipinfusion_ocnos | +| juniper | → | juniper | +| juniper_junos | → | juniper_junos | +| juniper_screenos | → | juniper_screenos | +| keymile | → | keymile | +| keymile_nos | → | keymile_nos | +| linux | → | linux | +| mellanox | → | mellanox | +| mellanox_mlnxos | → | mellanox_mlnxos | +| mikrotik_routeros | → | mikrotik_routeros | +| mikrotik_switchos | → | mikrotik_switchos | +| mrv_lx | → | mrv_lx | +| mrv_optiswitch | → | mrv_optiswitch | +| netapp_cdot | → | netapp_cdot | +| netgear_prosafe | → | netgear_prosafe | +| netscaler | → | netscaler | +| nokia_sros | → | nokia_sros | +| oneaccess_oneos | → | oneaccess_oneos | +| ovs_linux | → | ovs_linux | +| paloalto_panos | → | paloalto_panos | +| pluribus | → | pluribus | +| quanta_mesh | → | quanta_mesh | +| rad_etx | → | rad_etx | +| raisecom_roap | → | raisecom_roap | +| ruckus_fastiron | → | ruckus_fastiron | +| ruijie_os | → | ruijie_os | +| sixwind_os | → | sixwind_os | +| sophos_sfos | → | sophos_sfos | +| tplink_jetstream | → | tplink_jetstream | +| ubiquiti_edge | → | ubiquiti_edge | +| ubiquiti_edgerouter | → | ubiquiti_edgerouter | +| ubiquiti_edgeswitch | → | ubiquiti_edgeswitch | +| ubiquiti_unifiswitch | → | ubiquiti_unifiswitch | +| vyatta_vyos | → | vyatta_vyos | +| vyos | → | vyos | +| watchguard_fireware | → | watchguard_fireware | +| yamaha | → | yamaha | +| zte_zxros | → | zte_zxros | \ No newline at end of file diff --git a/docs/user/lib_mapper/netutilsparser.md b/docs/user/lib_mapper/netutilsparser.md new file mode 100644 index 00000000..52f4d9db --- /dev/null +++ b/docs/user/lib_mapper/netutilsparser.md @@ -0,0 +1,22 @@ +| NETUTILSPARSER | | NORMALIZED | +| ---------- | -- | ------ | +| arista_eos | → | arista_eos | +| aruba_aoscx | → | aruba_aoscx | +| bigip_f5 | → | bigip_f5 | +| cisco_aireos | → | cisco_aireos | +| cisco_asa | → | cisco_asa | +| cisco_ios | → | cisco_ios | +| cisco_iosxr | → | cisco_iosxr | +| cisco_nxos | → | cisco_nxos | +| citrix_netscaler | → | citrix_netscaler | +| extreme_netiron | → | extreme_netiron | +| fortinet_fortios | → | fortinet_fortios | +| juniper_junos | → | juniper_junos | +| linux | → | linux | +| mikrotik_routeros | → | mikrotik_routeros | +| mrv_optiswitch | → | mrv_optiswitch | +| netscaler | → | netscaler | +| nokia_sros | → | nokia_sros | +| paloalto_panos | → | paloalto_panos | +| ruckus_fastiron | → | ruckus_fastiron | +| ubiquiti_airos | → | ubiquiti_airos | \ No newline at end of file diff --git a/docs/user/lib_mapper/netutilsparser_reverse.md b/docs/user/lib_mapper/netutilsparser_reverse.md new file mode 100644 index 00000000..c6b2f36c --- /dev/null +++ b/docs/user/lib_mapper/netutilsparser_reverse.md @@ -0,0 +1,22 @@ +| NORMALIZED | | NETUTILSPARSER | +| ---------- | -- | ------ | +| arista_eos | → | arista_eos | +| aruba_aoscx | → | aruba_aoscx | +| bigip_f5 | → | bigip_f5 | +| cisco_aireos | → | cisco_aireos | +| cisco_asa | → | cisco_asa | +| cisco_ios | → | cisco_ios | +| cisco_iosxr | → | cisco_iosxr | +| cisco_nxos | → | cisco_nxos | +| citrix_netscaler | → | citrix_netscaler | +| extreme_netiron | → | extreme_netiron | +| fortinet_fortios | → | fortinet_fortios | +| juniper_junos | → | juniper_junos | +| linux | → | linux | +| mikrotik_routeros | → | mikrotik_routeros | +| mrv_optiswitch | → | mrv_optiswitch | +| netscaler | → | netscaler | +| nokia_sros | → | nokia_sros | +| paloalto_panos | → | paloalto_panos | +| ruckus_fastiron | → | ruckus_fastiron | +| ubiquiti_airos | → | ubiquiti_airos | \ No newline at end of file diff --git a/docs/user/lib_use_cases_lib_mapper.md b/docs/user/lib_use_cases_lib_mapper.md index 8987d1de..3ae96c22 100644 --- a/docs/user/lib_use_cases_lib_mapper.md +++ b/docs/user/lib_use_cases_lib_mapper.md @@ -47,6 +47,14 @@ Another use case could be using an example like the above in an Ansible filter. --8<-- "docs/user/lib_mapper/ansible_reverse.md" +## Hier Config Mapper + +--8<-- "docs/user/lib_mapper/hierconfig.md" + +## Reverse Hier Config Mapper + +--8<-- "docs/user/lib_mapper/hierconfig_reverse.md" + ## Napalm Mapper --8<-- "docs/user/lib_mapper/napalm.md" @@ -55,6 +63,23 @@ Another use case could be using an example like the above in an Ansible filter. --8<-- "docs/user/lib_mapper/napalm_reverse.md" +## Netmiko Mapper + +--8<-- "docs/user/lib_mapper/netmiko.md" + +## Reverse Netmiko Mapper + +--8<-- "docs/user/lib_mapper/netmiko_reverse.md" + + +## Netutils Parser Mapper + +--8<-- "docs/user/lib_mapper/netutilsparser.md" + +## Reverse Netutils Parser Mapper + +--8<-- "docs/user/lib_mapper/netutilsparser_reverse.md" + ## NTC Templates Mapper --8<-- "docs/user/lib_mapper/ntctemplates.md" @@ -84,10 +109,5 @@ Another use case could be using an example like the above in an Ansible filter. --8<-- "docs/user/lib_mapper/scrapli.md" ## Reverse Scrapli Mapper ---8<-- "docs/user/lib_mapper/scrapli_reverse.md" -## Hier Config Mapper ---8<-- "docs/user/lib_mapper/hierconfig.md" - -## Reverse Hier Config Mapper ---8<-- "docs/user/lib_mapper/hierconfig_reverse.md" \ No newline at end of file +--8<-- "docs/user/lib_mapper/scrapli_reverse.md" \ No newline at end of file diff --git a/netutils/config/compliance.py b/netutils/config/compliance.py index 76f25771..136fd2ae 100644 --- a/netutils/config/compliance.py +++ b/netutils/config/compliance.py @@ -8,23 +8,23 @@ parser_map: t.Dict[str, t.Type[parser.BaseConfigParser]] = { "arista_eos": parser.EOSConfigParser, + "aruba_aoscx": parser.ArubaConfigParser, + "bigip_f5": parser.F5ConfigParser, + "cisco_aireos": parser.AIREOSConfigParser, + "cisco_asa": parser.ASAConfigParser, "cisco_ios": parser.IOSConfigParser, "cisco_iosxr": parser.IOSXRConfigParser, "cisco_nxos": parser.NXOSConfigParser, - "cisco_aireos": parser.AIREOSConfigParser, "citrix_netscaler": parser.NetscalerConfigParser, - "linux": parser.LINUXConfigParser, - "bigip_f5": parser.F5ConfigParser, - "juniper_junos": parser.JunosConfigParser, - "cisco_asa": parser.ASAConfigParser, + "extreme_netiron": parser.NetironConfigParser, "fortinet_fortios": parser.FortinetConfigParser, - "nokia_sros": parser.NokiaConfigParser, - "netscaler": parser.NetscalerConfigParser, - "aruba_aoscx": parser.ArubaConfigParser, + "juniper_junos": parser.JunosConfigParser, + "linux": parser.LINUXConfigParser, + "mikrotik_routeros": parser.RouterOSConfigParser, "mrv_optiswitch": parser.OptiswitchConfigParser, - "extreme_netiron": parser.NetironConfigParser, + "netscaler": parser.NetscalerConfigParser, + "nokia_sros": parser.NokiaConfigParser, "paloalto_panos": parser.PaloAltoNetworksConfigParser, - "mikrotik_routeros": parser.RouterOSConfigParser, "ruckus_fastiron": parser.FastironConfigParser, "ubiquiti_airos": parser.UbiquitiAirOSConfigParser, } diff --git a/netutils/lib_mapper.py b/netutils/lib_mapper.py index 076deaff..c51e75dc 100644 --- a/netutils/lib_mapper.py +++ b/netutils/lib_mapper.py @@ -114,70 +114,71 @@ "yamaha": {}, } # netmiko is the base name, so every key is a value, this ensure that. -NETMIKO_LIB_MAPPER = {} -for key in list(_NETMIKO_LIB_MAPPER.keys()): - NETMIKO_LIB_MAPPER[key] = key +NETMIKO_LIB_MAPPER = {key: key for key in sorted(_NETMIKO_LIB_MAPPER)} # ntc templates is primarily based on netmiko, so a copy is in order -NTCTEMPLATES_LIB_MAPPER = copy.deepcopy(NETMIKO_LIB_MAPPER) -NTCTEMPLATES_LIB_MAPPER["watchguard_firebox"] = "watchguard_firebox" -NTCTEMPLATES_LIB_MAPPER["huawei_vrp"] = "huawei_vrp" -NTCTEMPLATES_LIB_MAPPER["vmware_nsxv"] = "vmware_nsxv" -NTCTEMPLATES_LIB_MAPPER["aruba_aoscx"] = "aruba_aoscx" +_NTCTEMPLATES_LIB_MAPPER = copy.deepcopy(NETMIKO_LIB_MAPPER) +_NTCTEMPLATES_LIB_MAPPER["aruba_aoscx"] = "aruba_aoscx" +_NTCTEMPLATES_LIB_MAPPER["huawei_vrp"] = "huawei_vrp" +_NTCTEMPLATES_LIB_MAPPER["vmware_nsxv"] = "vmware_nsxv" +_NTCTEMPLATES_LIB_MAPPER["watchguard_firebox"] = "watchguard_firebox" +NTCTEMPLATES_LIB_MAPPER = {key: _NTCTEMPLATES_LIB_MAPPER[key] for key in sorted(_NTCTEMPLATES_LIB_MAPPER)} NAPALM_LIB_MAPPER = { + "aoscx": "aruba_aoscx", "asa": "cisco_asa", "cisco_wlc_ssh": "cisco_wlc", "eos": "arista_eos", + "f5": "bigip_f5", "fortios": "fortinet", "huawei": "huawei_vrp", "ios": "cisco_ios", - "nxos_ssh": "cisco_nxos", - "nxos": "cisco_nxos", "iosxr": "cisco_xr", "junos": "juniper_junos", + "nxos": "cisco_nxos", + "nxos_ssh": "cisco_nxos", "panos": "paloalto_panos", + "ros": "mikrotik_routeros", "sros": "nokia_sros", "vyos": "brocade_vyos", - "aoscx": "aruba_aoscx", - "ros": "mikrotik_routeros", } PYNTC_LIB_MAPPER = { - "cisco_asa_ssh": "cisco_asa", "arista_eos_eapi": "arista_eos", - "f5_tmos_icontrol": "f5_tmsh", + "cisco_aireos_ssh": "cisco_wlc", + "cisco_asa_ssh": "cisco_asa", "cisco_ios_ssh": "cisco_ios", - "juniper_junos_netconf": "juniper_junos", "cisco_nxos_nxapi": "cisco_nxos", - "cisco_aireos_ssh": "cisco_wlc", + "f5_tmos_icontrol": "f5_tmsh", + "juniper_junos_netconf": "juniper_junos", } + ANSIBLE_LIB_MAPPER = { "arista.eos.eos": "arista_eos", + "arubanetworks.aoscx": "aruba_aoscx", "ciena.saos6.saos6": "ciena_saos", "cisco.asa.asa": "cisco_asa", "cisco.ios.ios": "cisco_ios", "cisco.iosxr.iosxr": "cisco_xr", "cisco.nxos.nxos": "cisco_nxos", "community.network.ce": "huawei", - "dellemc.os6.os6": "dell_os6", - "dellemc.os9.os9": "dell_os9", - "dellemc.os10.0s10": "dell_os10", + "community.network.cnos": "lenovo_cnos", + "community.network.enos": "lenovo_enos", "community.network.eric_eccli": "ericsson_ipos", "community.network.exos": "extreme_exos", + "community.network.icx": "ruckus_icx", "community.network.ironware": "extreme_netiron", + "community.network.netvisor": "pluribus", "community.network.nos": "extreme_nos", + "community.network.routeros": "mikrotik_routeros", "community.network.slxos": "extreme_slx", + "community.network.sros": "nokia_sros", "community.network.voss": "extreme_vsp", + "dellemc.os10.0s10": "dell_os10", + "dellemc.os6.os6": "dell_os6", + "dellemc.os9.os9": "dell_os9", "junipernetworks.junos.junos": "juniper_junos", - "community.network.cnos": "lenovo_cnos", - "community.network.enos": "lenovo_enos", - "community.network.routeros": "mikrotik_routeros", - "community.network.netvisor": "pluribus", - "community.network.icx": "ruckus_icx", - "community.network.sros": "nokia_sros", "vyos.vyos.vyos": "vyos", - "arubanetworks.aoscx": "aruba_aoscx", } PYATS_LIB_MAPPER = { @@ -195,66 +196,92 @@ } SCRAPLI_LIB_MAPPER = { + "arista_eos": "arista_eos", + "aruba_aoscx": "aruba_aoscx", "cisco_iosxe": "cisco_ios", "cisco_iosxr": "cisco_xr", "cisco_nxos": "cisco_nxos", - "arista_eos": "arista_eos", "juniper_junos": "juniper_junos", - "aruba_aoscx": "aruba_aoscx", } HIERCONFIG_LIB_MAPPER = { + "eos": "arista_eos", + "fastiron": "ruckus_fastiron", "ios": "cisco_ios", "iosxe": "cisco_xe", "iosxr": "cisco_xr", "nxos": "cisco_nxos", - "eos": "arista_eos", - "fastiron": "ruckus_fastiron", +} + +NETUTILSPARSER_LIB_MAPPER = { + "arista_eos": "arista_eos", + "aruba_aoscx": "aruba_aoscx", + "bigip_f5": "bigip_f5", + "cisco_aireos": "cisco_aireos", + "cisco_asa": "cisco_asa", + "cisco_ios": "cisco_ios", + "cisco_iosxr": "cisco_iosxr", + "cisco_nxos": "cisco_nxos", + "citrix_netscaler": "citrix_netscaler", + "extreme_netiron": "extreme_netiron", + "fortinet_fortios": "fortinet_fortios", + "juniper_junos": "juniper_junos", + "linux": "linux", + "mikrotik_routeros": "mikrotik_routeros", + "mrv_optiswitch": "mrv_optiswitch", + "netscaler": "netscaler", + "nokia_sros": "nokia_sros", + "paloalto_panos": "paloalto_panos", + "ruckus_fastiron": "ruckus_fastiron", + "ubiquiti_airos": "ubiquiti_airos", } NAPALM_LIB_MAPPER_REVERSE = { "arista_eos": "eos", + "aruba_aoscx": "aoscx", + "bigip_f5": "f5", "brocade_vyos": "vyos", "cisco_asa": "asa", "cisco_ios": "ios", "cisco_nxos": "nxos", - "cisco_xr": "iosxr", "cisco_wlc": "cisco_wlc_ssh", + "cisco_xr": "iosxr", "fortinet": "fortios", "huawei_vrp": "huawei", "juniper_junos": "junos", - "paloalto_panos": "panos", - "nokia_sros": "sros", - "aruba_aoscx": "aoscx", "mikrotik_routeros": "ros", + "nokia_sros": "sros", + "paloalto_panos": "panos", } PYNTC_LIB_MAPPER_REVERSE = { - "cisco_asa": "cisco_asa_ssh", "arista_eos": "arista_eos_eapi", - "f5_tmsh": "f5_tmos_icontrol", + "cisco_asa": "cisco_asa_ssh", "cisco_ios": "cisco_ios_ssh", - "juniper_junos": "juniper_junos_netconf", "cisco_nxos": "cisco_nxos_nxapi", "cisco_wlc": "cisco_aireos_ssh", + "f5_tmsh": "f5_tmos_icontrol", + "juniper_junos": "juniper_junos_netconf", } + ANSIBLE_LIB_MAPPER_REVERSE = { "arista_eos": "arista.eos.eos", + "aruba_aoscx": "arubanetworks.aoscx", "ciena_saos": "ciena.saos6.saos6", "cisco_asa": "cisco.asa.asa", "cisco_ios": "cisco.ios.ios", - "cisco_xr": "cisco.iosxr.iosxr", "cisco_nxos": "cisco.nxos.nxos", - "huawei": "community.network.ce", + "cisco_xr": "cisco.iosxr.iosxr", + "dell_os10": "dellemc.os10.0s10", "dell_os6": "dellemc.os6.os6", "dell_os9": "dellemc.os9.os9", - "dell_os10": "dellemc.os10.0s10", "ericsson_ipos": "community.network.eric_eccli", "extreme_exos": "community.network.exos", "extreme_netiron": "community.network.ironware", "extreme_nos": "community.network.nos", "extreme_slx": "community.network.slxos", "extreme_vsp": "community.network.voss", + "huawei": "community.network.ce", "juniper_junos": "junipernetworks.junos.junos", "lenovo_cnos": "community.network.cnos", "lenovo_enos": "community.network.enos", @@ -263,53 +290,76 @@ "pluribus": "community.network.netvisor", "ruckus_icx": "community.network.icx", "vyos": "vyos.vyos.vyos", - "aruba_aoscx": "arubanetworks.aoscx", } PYATS_LIB_MAPPER_REVERSE = { "cisco_asa": "asa", - "f5_tmsh": "bigip", "cisco_dnac": "dnac", "cisco_ios": "iosxe", + "cisco_nxos": "nxos", + "cisco_viptella": "viptela", "cisco_xr": "iosxr", + "f5_tmsh": "bigip", "juniper_junos": "junos", "linux": "linux", - "cisco_nxos": "nxos", "nokia_sros": "sros", - "cisco_viptella": "viptela", } SCRAPLI_LIB_MAPPER_REVERSE = { + "arista_eos": "arista_eos", + "aruba_aoscx": "aruba_aoscx", "cisco_ios": "cisco_iosxe", - "cisco_xr": "cisco_iosxr", "cisco_nxos": "cisco_nxos", - "arista_eos": "arista_eos", + "cisco_xr": "cisco_iosxr", "juniper_junos": "juniper_junos", - "aruba_aoscx": "aruba_aoscx", } HIERCONFIG_LIB_MAPPER_REVERSE = { + "arista_eos": "eos", "cisco_ios": "ios", + "cisco_nxos": "nxos", "cisco_xe": "iosxe", "cisco_xr": "iosxr", - "cisco_nxos": "nxos", - "arista_eos": "eos", "ruckus_fastiron": "fastiron", } +NETUTILSPARSER_LIB_MAPPER_REVERSE = { + "arista_eos": "arista_eos", + "aruba_aoscx": "aruba_aoscx", + "bigip_f5": "bigip_f5", + "cisco_aireos": "cisco_aireos", + "cisco_asa": "cisco_asa", + "cisco_ios": "cisco_ios", + "cisco_iosxr": "cisco_iosxr", + "cisco_nxos": "cisco_nxos", + "citrix_netscaler": "citrix_netscaler", + "extreme_netiron": "extreme_netiron", + "fortinet_fortios": "fortinet_fortios", + "juniper_junos": "juniper_junos", + "linux": "linux", + "mikrotik_routeros": "mikrotik_routeros", + "mrv_optiswitch": "mrv_optiswitch", + "netscaler": "netscaler", + "nokia_sros": "nokia_sros", + "paloalto_panos": "paloalto_panos", + "ruckus_fastiron": "ruckus_fastiron", + "ubiquiti_airos": "ubiquiti_airos", +} + # Deep copy the reverse, where there is no actual translation happening. NETMIKO_LIB_MAPPER_REVERSE = copy.deepcopy(NETMIKO_LIB_MAPPER) NTCTEMPLATES_LIB_MAPPER_REVERSE = copy.deepcopy(NTCTEMPLATES_LIB_MAPPER) # Deep copy the reverse, where there is no actual translation happening with special # consideration for OS's not in netmiko. -MAIN_LIB_MAPPER = copy.deepcopy(NETMIKO_LIB_MAPPER) -MAIN_LIB_MAPPER["cisco_dnac"] = "cisco_dnac" -MAIN_LIB_MAPPER["cisco_viptella"] = "cisco_viptella" -MAIN_LIB_MAPPER["huawei_vrp"] = "huawei_vrp" -MAIN_LIB_MAPPER["lenovo_cnos"] = "lenovo_cnos" -MAIN_LIB_MAPPER["lenovo_enos"] = "lenovo_enos" -MAIN_LIB_MAPPER["ruckus_icx"] = "ruckus_icx" -MAIN_LIB_MAPPER["vmware_nsxv"] = "vmware_nsxv" -MAIN_LIB_MAPPER["watchguard_firebox"] = "watchguard_firebox" -MAIN_LIB_MAPPER["aruba_aoscx"] = "aruba_aoscx" +_MAIN_LIB_MAPPER = copy.deepcopy(NETMIKO_LIB_MAPPER) +_MAIN_LIB_MAPPER["aruba_aoscx"] = "aruba_aoscx" +_MAIN_LIB_MAPPER["cisco_dnac"] = "cisco_dnac" +_MAIN_LIB_MAPPER["cisco_viptella"] = "cisco_viptella" +_MAIN_LIB_MAPPER["huawei_vrp"] = "huawei_vrp" +_MAIN_LIB_MAPPER["lenovo_cnos"] = "lenovo_cnos" +_MAIN_LIB_MAPPER["lenovo_enos"] = "lenovo_enos" +_MAIN_LIB_MAPPER["ruckus_icx"] = "ruckus_icx" +_MAIN_LIB_MAPPER["vmware_nsxv"] = "vmware_nsxv" +_MAIN_LIB_MAPPER["watchguard_firebox"] = "watchguard_firebox" +MAIN_LIB_MAPPER = {key: _MAIN_LIB_MAPPER[key] for key in sorted(_MAIN_LIB_MAPPER)} diff --git a/tests/unit/test_lib_mapper.py b/tests/unit/test_lib_mapper.py index e0fa2e5c..40a9a923 100644 --- a/tests/unit/test_lib_mapper.py +++ b/tests/unit/test_lib_mapper.py @@ -2,6 +2,9 @@ import pytest from netutils import lib_mapper +from netutils.config.compliance import parser_map + +LIBRARIES = ["ANSIBLE", "NETMIKO", "NTCTEMPLATES", "NAPALM", "PYATS", "PYNTC", "HIERCONFIG", "NETUTILSPARSER"] def test_lib_mapper(): @@ -13,6 +16,7 @@ def test_lib_mapper(): assert len(lib_mapper.PYATS_LIB_MAPPER.keys()) > 10 assert len(lib_mapper.PYNTC_LIB_MAPPER.keys()) > 5 assert len(lib_mapper.HIERCONFIG_LIB_MAPPER.keys()) > 4 + assert len(lib_mapper.NETUTILSPARSER_LIB_MAPPER.keys()) > 10 assert lib_mapper.ANSIBLE_LIB_MAPPER["cisco.ios.ios"] == "cisco_ios" assert lib_mapper.NAPALM_LIB_MAPPER["ios"] == "cisco_ios" assert lib_mapper.PYNTC_LIB_MAPPER["arista_eos_eapi"] == "arista_eos" @@ -21,8 +25,27 @@ def test_lib_mapper(): assert lib_mapper.HIERCONFIG_LIB_MAPPER_REVERSE[lib_mapper.HIERCONFIG_LIB_MAPPER["ios"]] == "ios" -@pytest.mark.parametrize("lib", ["ANSIBLE", "NETMIKO", "NTCTEMPLATES", "NAPALM", "PYATS", "PYNTC", "HIERCONFIG"]) +@pytest.mark.parametrize("lib", LIBRARIES) def test_lib_mapper_reverse(lib): - mapper = dict((v, k) for k, v in getattr(lib_mapper, f"{lib}_LIB_MAPPER").items()) + _mapper = getattr(lib_mapper, f"{lib}_LIB_MAPPER").copy() + if lib == "NAPALM": + _mapper.pop("nxos_ssh") + mapper = dict((v, k) for k, v in _mapper.items()) rev_mapper = getattr(lib_mapper, f"{lib}_LIB_MAPPER_REVERSE") assert mapper == rev_mapper + + +@pytest.mark.parametrize("lib", LIBRARIES) +def test_lib_mapper_alpha(lib): + original = list(getattr(lib_mapper, f"{lib}_LIB_MAPPER").keys()) + sorted_keys = sorted(original) + rev_original = list(getattr(lib_mapper, f"{lib}_LIB_MAPPER_REVERSE").keys()) + rev_sorted_keys = sorted(rev_original) + assert original == sorted_keys + assert rev_original == rev_sorted_keys + + +def test_netutils_parser(): + """Test that the paser_map in compliance have been added to NETUTILSPARSER lib mappers.""" + assert parser_map.keys() == lib_mapper.NETUTILSPARSER_LIB_MAPPER.keys() + assert list(parser_map.keys()) == sorted(list(lib_mapper.NETUTILSPARSER_LIB_MAPPER.keys())) From 835d8d3d22b044f335c130bf16b1cff555b9c620 Mon Sep 17 00:00:00 2001 From: Adam Byczkowski <38091261+qduk@users.noreply.github.com> Date: Thu, 7 Sep 2023 20:09:55 -0500 Subject: [PATCH 20/21] Changed flatbot schedule (#358) --- .github/workflows/oui_filepull.yml | 2 +- .github/workflows/proto_filepull.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/oui_filepull.yml b/.github/workflows/oui_filepull.yml index ab396a64..bc78d92d 100644 --- a/.github/workflows/oui_filepull.yml +++ b/.github/workflows/oui_filepull.yml @@ -3,7 +3,7 @@ name: "OUI-Updates" on: # yamllint disable-line rule:truthy schedule: - - cron: "0 8 * * 4" + - cron: "0 2 1 * *" jobs: data_gathering: diff --git a/.github/workflows/proto_filepull.yml b/.github/workflows/proto_filepull.yml index bf02c00c..f41c4452 100644 --- a/.github/workflows/proto_filepull.yml +++ b/.github/workflows/proto_filepull.yml @@ -3,7 +3,7 @@ name: "Protocol-Updates" on: # yamllint disable-line rule:truthy schedule: - - cron: "0 8 * * 4" + - cron: "0 2 1 * *" jobs: data_gathering: From 93597ec3d1a44b8c75a427c92de0d02eab90fd57 Mon Sep 17 00:00:00 2001 From: Ken Celenza Date: Fri, 8 Sep 2023 15:41:51 -0400 Subject: [PATCH 21/21] Prep for a 1.6.0 release (#367) --- docs/admin/release_notes/version_1.6.md | 36 +++ mkdocs.yml | 1 + poetry.lock | 362 ++++++++++++------------ pyproject.toml | 2 +- 4 files changed, 224 insertions(+), 177 deletions(-) create mode 100755 docs/admin/release_notes/version_1.6.md diff --git a/docs/admin/release_notes/version_1.6.md b/docs/admin/release_notes/version_1.6.md new file mode 100755 index 00000000..d0189296 --- /dev/null +++ b/docs/admin/release_notes/version_1.6.md @@ -0,0 +1,36 @@ +# v1.6 Release Notes + +## Release Overview + +- Added `ubiquiti_airos` parser. +- Added `ACLRule` and `ACLRules` classes. +- Added the `ip` functions `is_ip_range`, `is_ip_within`, `is_network`, and `get_range_ips`. +- Added the `asn` function `int_to_asn` in the function. +- Added `NETUTILSPARSER_LIB_MAPPER` and `NETUTILSPARSER_LIB_MAPPER_REVERSE` to lib mapper. +- Added classy docs to view config parser classes. + + +## [v1.6.0] - 2023-09 + +### Added + +- [#290](https://github.com/networktocode/netutils/pull/290) Added `ACLRule` and `ACLRules` classes. +- [#290](https://github.com/networktocode/netutils/pull/290) Added the `ip` functions `is_ip_range`, `is_ip_within`, `is_network`, and `get_range_ips`. +- [#325](https://github.com/networktocode/netutils/pull/325) Added classy docs to view config parser classes. +- [#329](https://github.com/networktocode/netutils/pull/329) Added `ubiquiti_airos` parser. +- [#332](https://github.com/networktocode/netutils/pull/332) Added the `asn` function `int_to_asn` in the function. +- [#352](https://github.com/networktocode/netutils/pull/352) Added `NETUTILSPARSER_LIB_MAPPER` and `NETUTILSPARSER_LIB_MAPPER_REVERSE` to lib mapper. + +### Changed + +- [#346](https://github.com/networktocode/netutils/pull/346) Updated `mac_to_format` docs. +- [#352](https://github.com/networktocode/netutils/pull/352) Clean up docs and sorting for lib mapper. +- [#358](https://github.com/networktocode/netutils/pull/358) Changed flatbot schedule. + +### Fixed + +- [#339](https://github.com/networktocode/netutils/pull/339) Fix for ASN functions. +- [#340](https://github.com/networktocode/netutils/pull/340) Fixed Netscaler parser slug. +- [#346](https://github.com/networktocode/netutils/pull/346) Fix issue where interface abbreviation not working. +- [#350](https://github.com/networktocode/netutils/pull/350) Update library mappings to be correct order. +- [#356](https://github.com/networktocode/netutils/pull/356) Fixed mkdocs dependencies. diff --git a/mkdocs.yml b/mkdocs.yml index e4c71716..932d02ca 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -125,6 +125,7 @@ nav: - v1.3: "admin/release_notes/version_1.3.md" - v1.4: "admin/release_notes/version_1.4.md" - v1.5: "admin/release_notes/version_1.5.md" + - v1.6: "admin/release_notes/version_1.6.md" - Developer Guide: - Extending the Library: "dev/extending.md" - Contributing to the Library: "dev/contributing.md" diff --git a/poetry.lock b/poetry.lock index e42be1e0..9130579b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -385,63 +385,63 @@ files = [ [[package]] name = "coverage" -version = "7.3.0" +version = "7.3.1" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:db76a1bcb51f02b2007adacbed4c88b6dee75342c37b05d1822815eed19edee5"}, - {file = "coverage-7.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c02cfa6c36144ab334d556989406837336c1d05215a9bdf44c0bc1d1ac1cb637"}, - {file = "coverage-7.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:477c9430ad5d1b80b07f3c12f7120eef40bfbf849e9e7859e53b9c93b922d2af"}, - {file = "coverage-7.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce2ee86ca75f9f96072295c5ebb4ef2a43cecf2870b0ca5e7a1cbdd929cf67e1"}, - {file = "coverage-7.3.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68d8a0426b49c053013e631c0cdc09b952d857efa8f68121746b339912d27a12"}, - {file = "coverage-7.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b3eb0c93e2ea6445b2173da48cb548364f8f65bf68f3d090404080d338e3a689"}, - {file = "coverage-7.3.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:90b6e2f0f66750c5a1178ffa9370dec6c508a8ca5265c42fbad3ccac210a7977"}, - {file = "coverage-7.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:96d7d761aea65b291a98c84e1250cd57b5b51726821a6f2f8df65db89363be51"}, - {file = "coverage-7.3.0-cp310-cp310-win32.whl", hash = "sha256:63c5b8ecbc3b3d5eb3a9d873dec60afc0cd5ff9d9f1c75981d8c31cfe4df8527"}, - {file = "coverage-7.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:97c44f4ee13bce914272589b6b41165bbb650e48fdb7bd5493a38bde8de730a1"}, - {file = "coverage-7.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:74c160285f2dfe0acf0f72d425f3e970b21b6de04157fc65adc9fd07ee44177f"}, - {file = "coverage-7.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b543302a3707245d454fc49b8ecd2c2d5982b50eb63f3535244fd79a4be0c99d"}, - {file = "coverage-7.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad0f87826c4ebd3ef484502e79b39614e9c03a5d1510cfb623f4a4a051edc6fd"}, - {file = "coverage-7.3.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:13c6cbbd5f31211d8fdb477f0f7b03438591bdd077054076eec362cf2207b4a7"}, - {file = "coverage-7.3.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fac440c43e9b479d1241fe9d768645e7ccec3fb65dc3a5f6e90675e75c3f3e3a"}, - {file = "coverage-7.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:3c9834d5e3df9d2aba0275c9f67989c590e05732439b3318fa37a725dff51e74"}, - {file = "coverage-7.3.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4c8e31cf29b60859876474034a83f59a14381af50cbe8a9dbaadbf70adc4b214"}, - {file = "coverage-7.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7a9baf8e230f9621f8e1d00c580394a0aa328fdac0df2b3f8384387c44083c0f"}, - {file = "coverage-7.3.0-cp311-cp311-win32.whl", hash = "sha256:ccc51713b5581e12f93ccb9c5e39e8b5d4b16776d584c0f5e9e4e63381356482"}, - {file = "coverage-7.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:887665f00ea4e488501ba755a0e3c2cfd6278e846ada3185f42d391ef95e7e70"}, - {file = "coverage-7.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d000a739f9feed900381605a12a61f7aaced6beae832719ae0d15058a1e81c1b"}, - {file = "coverage-7.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:59777652e245bb1e300e620ce2bef0d341945842e4eb888c23a7f1d9e143c446"}, - {file = "coverage-7.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9737bc49a9255d78da085fa04f628a310c2332b187cd49b958b0e494c125071"}, - {file = "coverage-7.3.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5247bab12f84a1d608213b96b8af0cbb30d090d705b6663ad794c2f2a5e5b9fe"}, - {file = "coverage-7.3.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2ac9a1de294773b9fa77447ab7e529cf4fe3910f6a0832816e5f3d538cfea9a"}, - {file = "coverage-7.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:85b7335c22455ec12444cec0d600533a238d6439d8d709d545158c1208483873"}, - {file = "coverage-7.3.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:36ce5d43a072a036f287029a55b5c6a0e9bd73db58961a273b6dc11a2c6eb9c2"}, - {file = "coverage-7.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:211a4576e984f96d9fce61766ffaed0115d5dab1419e4f63d6992b480c2bd60b"}, - {file = "coverage-7.3.0-cp312-cp312-win32.whl", hash = "sha256:56afbf41fa4a7b27f6635bc4289050ac3ab7951b8a821bca46f5b024500e6321"}, - {file = "coverage-7.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:7f297e0c1ae55300ff688568b04ff26b01c13dfbf4c9d2b7d0cb688ac60df479"}, - {file = "coverage-7.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ac0dec90e7de0087d3d95fa0533e1d2d722dcc008bc7b60e1143402a04c117c1"}, - {file = "coverage-7.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:438856d3f8f1e27f8e79b5410ae56650732a0dcfa94e756df88c7e2d24851fcd"}, - {file = "coverage-7.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1084393c6bda8875c05e04fce5cfe1301a425f758eb012f010eab586f1f3905e"}, - {file = "coverage-7.3.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49ab200acf891e3dde19e5aa4b0f35d12d8b4bd805dc0be8792270c71bd56c54"}, - {file = "coverage-7.3.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a67e6bbe756ed458646e1ef2b0778591ed4d1fcd4b146fc3ba2feb1a7afd4254"}, - {file = "coverage-7.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8f39c49faf5344af36042b293ce05c0d9004270d811c7080610b3e713251c9b0"}, - {file = "coverage-7.3.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7df91fb24c2edaabec4e0eee512ff3bc6ec20eb8dccac2e77001c1fe516c0c84"}, - {file = "coverage-7.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:34f9f0763d5fa3035a315b69b428fe9c34d4fc2f615262d6be3d3bf3882fb985"}, - {file = "coverage-7.3.0-cp38-cp38-win32.whl", hash = "sha256:bac329371d4c0d456e8d5f38a9b0816b446581b5f278474e416ea0c68c47dcd9"}, - {file = "coverage-7.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:b859128a093f135b556b4765658d5d2e758e1fae3e7cc2f8c10f26fe7005e543"}, - {file = "coverage-7.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fc0ed8d310afe013db1eedd37176d0839dc66c96bcfcce8f6607a73ffea2d6ba"}, - {file = "coverage-7.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61260ec93f99f2c2d93d264b564ba912bec502f679793c56f678ba5251f0393"}, - {file = "coverage-7.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97af9554a799bd7c58c0179cc8dbf14aa7ab50e1fd5fa73f90b9b7215874ba28"}, - {file = "coverage-7.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3558e5b574d62f9c46b76120a5c7c16c4612dc2644c3d48a9f4064a705eaee95"}, - {file = "coverage-7.3.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37d5576d35fcb765fca05654f66aa71e2808d4237d026e64ac8b397ffa66a56a"}, - {file = "coverage-7.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:07ea61bcb179f8f05ffd804d2732b09d23a1238642bf7e51dad62082b5019b34"}, - {file = "coverage-7.3.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:80501d1b2270d7e8daf1b64b895745c3e234289e00d5f0e30923e706f110334e"}, - {file = "coverage-7.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4eddd3153d02204f22aef0825409091a91bf2a20bce06fe0f638f5c19a85de54"}, - {file = "coverage-7.3.0-cp39-cp39-win32.whl", hash = "sha256:2d22172f938455c156e9af2612650f26cceea47dc86ca048fa4e0b2d21646ad3"}, - {file = "coverage-7.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:60f64e2007c9144375dd0f480a54d6070f00bb1a28f65c408370544091c9bc9e"}, - {file = "coverage-7.3.0-pp38.pp39.pp310-none-any.whl", hash = "sha256:5492a6ce3bdb15c6ad66cb68a0244854d9917478877a25671d70378bdc8562d0"}, - {file = "coverage-7.3.0.tar.gz", hash = "sha256:49dbb19cdcafc130f597d9e04a29d0a032ceedf729e41b181f51cd170e6ee865"}, + {file = "coverage-7.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cd0f7429ecfd1ff597389907045ff209c8fdb5b013d38cfa7c60728cb484b6e3"}, + {file = "coverage-7.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:966f10df9b2b2115da87f50f6a248e313c72a668248be1b9060ce935c871f276"}, + {file = "coverage-7.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0575c37e207bb9b98b6cf72fdaaa18ac909fb3d153083400c2d48e2e6d28bd8e"}, + {file = "coverage-7.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:245c5a99254e83875c7fed8b8b2536f040997a9b76ac4c1da5bff398c06e860f"}, + {file = "coverage-7.3.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c96dd7798d83b960afc6c1feb9e5af537fc4908852ef025600374ff1a017392"}, + {file = "coverage-7.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:de30c1aa80f30af0f6b2058a91505ea6e36d6535d437520067f525f7df123887"}, + {file = "coverage-7.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:50dd1e2dd13dbbd856ffef69196781edff26c800a74f070d3b3e3389cab2600d"}, + {file = "coverage-7.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b9c0c19f70d30219113b18fe07e372b244fb2a773d4afde29d5a2f7930765136"}, + {file = "coverage-7.3.1-cp310-cp310-win32.whl", hash = "sha256:770f143980cc16eb601ccfd571846e89a5fe4c03b4193f2e485268f224ab602f"}, + {file = "coverage-7.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:cdd088c00c39a27cfa5329349cc763a48761fdc785879220d54eb785c8a38520"}, + {file = "coverage-7.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:74bb470399dc1989b535cb41f5ca7ab2af561e40def22d7e188e0a445e7639e3"}, + {file = "coverage-7.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:025ded371f1ca280c035d91b43252adbb04d2aea4c7105252d3cbc227f03b375"}, + {file = "coverage-7.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6191b3a6ad3e09b6cfd75b45c6aeeffe7e3b0ad46b268345d159b8df8d835f9"}, + {file = "coverage-7.3.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7eb0b188f30e41ddd659a529e385470aa6782f3b412f860ce22b2491c89b8593"}, + {file = "coverage-7.3.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75c8f0df9dfd8ff745bccff75867d63ef336e57cc22b2908ee725cc552689ec8"}, + {file = "coverage-7.3.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7eb3cd48d54b9bd0e73026dedce44773214064be93611deab0b6a43158c3d5a0"}, + {file = "coverage-7.3.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ac3c5b7e75acac31e490b7851595212ed951889918d398b7afa12736c85e13ce"}, + {file = "coverage-7.3.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5b4ee7080878077af0afa7238df1b967f00dc10763f6e1b66f5cced4abebb0a3"}, + {file = "coverage-7.3.1-cp311-cp311-win32.whl", hash = "sha256:229c0dd2ccf956bf5aeede7e3131ca48b65beacde2029f0361b54bf93d36f45a"}, + {file = "coverage-7.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:c6f55d38818ca9596dc9019eae19a47410d5322408140d9a0076001a3dcb938c"}, + {file = "coverage-7.3.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5289490dd1c3bb86de4730a92261ae66ea8d44b79ed3cc26464f4c2cde581fbc"}, + {file = "coverage-7.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ca833941ec701fda15414be400c3259479bfde7ae6d806b69e63b3dc423b1832"}, + {file = "coverage-7.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd694e19c031733e446c8024dedd12a00cda87e1c10bd7b8539a87963685e969"}, + {file = "coverage-7.3.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aab8e9464c00da5cb9c536150b7fbcd8850d376d1151741dd0d16dfe1ba4fd26"}, + {file = "coverage-7.3.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87d38444efffd5b056fcc026c1e8d862191881143c3aa80bb11fcf9dca9ae204"}, + {file = "coverage-7.3.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8a07b692129b8a14ad7a37941a3029c291254feb7a4237f245cfae2de78de037"}, + {file = "coverage-7.3.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:2829c65c8faaf55b868ed7af3c7477b76b1c6ebeee99a28f59a2cb5907a45760"}, + {file = "coverage-7.3.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1f111a7d85658ea52ffad7084088277135ec5f368457275fc57f11cebb15607f"}, + {file = "coverage-7.3.1-cp312-cp312-win32.whl", hash = "sha256:c397c70cd20f6df7d2a52283857af622d5f23300c4ca8e5bd8c7a543825baa5a"}, + {file = "coverage-7.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:5ae4c6da8b3d123500f9525b50bf0168023313963e0e2e814badf9000dd6ef92"}, + {file = "coverage-7.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ca70466ca3a17460e8fc9cea7123c8cbef5ada4be3140a1ef8f7b63f2f37108f"}, + {file = "coverage-7.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f2781fd3cabc28278dc982a352f50c81c09a1a500cc2086dc4249853ea96b981"}, + {file = "coverage-7.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6407424621f40205bbe6325686417e5e552f6b2dba3535dd1f90afc88a61d465"}, + {file = "coverage-7.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:04312b036580ec505f2b77cbbdfb15137d5efdfade09156961f5277149f5e344"}, + {file = "coverage-7.3.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac9ad38204887349853d7c313f53a7b1c210ce138c73859e925bc4e5d8fc18e7"}, + {file = "coverage-7.3.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:53669b79f3d599da95a0afbef039ac0fadbb236532feb042c534fbb81b1a4e40"}, + {file = "coverage-7.3.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:614f1f98b84eb256e4f35e726bfe5ca82349f8dfa576faabf8a49ca09e630086"}, + {file = "coverage-7.3.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f1a317fdf5c122ad642db8a97964733ab7c3cf6009e1a8ae8821089993f175ff"}, + {file = "coverage-7.3.1-cp38-cp38-win32.whl", hash = "sha256:defbbb51121189722420a208957e26e49809feafca6afeef325df66c39c4fdb3"}, + {file = "coverage-7.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:f4f456590eefb6e1b3c9ea6328c1e9fa0f1006e7481179d749b3376fc793478e"}, + {file = "coverage-7.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f12d8b11a54f32688b165fd1a788c408f927b0960984b899be7e4c190ae758f1"}, + {file = "coverage-7.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f09195dda68d94a53123883de75bb97b0e35f5f6f9f3aa5bf6e496da718f0cb6"}, + {file = "coverage-7.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6601a60318f9c3945be6ea0f2a80571f4299b6801716f8a6e4846892737ebe4"}, + {file = "coverage-7.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07d156269718670d00a3b06db2288b48527fc5f36859425ff7cec07c6b367745"}, + {file = "coverage-7.3.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:636a8ac0b044cfeccae76a36f3b18264edcc810a76a49884b96dd744613ec0b7"}, + {file = "coverage-7.3.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5d991e13ad2ed3aced177f524e4d670f304c8233edad3210e02c465351f785a0"}, + {file = "coverage-7.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:586649ada7cf139445da386ab6f8ef00e6172f11a939fc3b2b7e7c9082052fa0"}, + {file = "coverage-7.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4aba512a15a3e1e4fdbfed2f5392ec221434a614cc68100ca99dcad7af29f3f8"}, + {file = "coverage-7.3.1-cp39-cp39-win32.whl", hash = "sha256:6bc6f3f4692d806831c136c5acad5ccedd0262aa44c087c46b7101c77e139140"}, + {file = "coverage-7.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:553d7094cb27db58ea91332e8b5681bac107e7242c23f7629ab1316ee73c4981"}, + {file = "coverage-7.3.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:220eb51f5fb38dfdb7e5d54284ca4d0cd70ddac047d750111a68ab1798945194"}, + {file = "coverage-7.3.1.tar.gz", hash = "sha256:6cb7fe1581deb67b782c153136541e20901aa312ceedaf1467dcb35255787952"}, ] [package.extras] @@ -590,13 +590,13 @@ smmap = ">=3.0.1,<6" [[package]] name = "gitpython" -version = "3.1.32" +version = "3.1.35" description = "GitPython is a Python library used to interact with Git repositories" optional = false python-versions = ">=3.7" files = [ - {file = "GitPython-3.1.32-py3-none-any.whl", hash = "sha256:e3d59b1c2c6ebb9dfa7a184daf3b6dd4914237e7488a1730a6d8f6f5d0b4187f"}, - {file = "GitPython-3.1.32.tar.gz", hash = "sha256:8d9b8cb1e80b9735e8717c9362079d3ce4c6e5ddeebedd0361b228c3a67a62f6"}, + {file = "GitPython-3.1.35-py3-none-any.whl", hash = "sha256:c19b4292d7a1d3c0f653858db273ff8a6614100d1eb1528b014ec97286193c09"}, + {file = "GitPython-3.1.35.tar.gz", hash = "sha256:9cbefbd1789a5fe9bcf621bb34d3f441f3a90c8461d377f84eda73e721d9b06b"}, ] [package.dependencies] @@ -604,13 +604,13 @@ gitdb = ">=4.0.1,<5" [[package]] name = "griffe" -version = "0.35.2" +version = "0.36.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-0.35.2-py3-none-any.whl", hash = "sha256:9650d6d0369c22f29f2c1bec9548ddc7f448f8ca38698a5799f92f736824e749"}, - {file = "griffe-0.35.2.tar.gz", hash = "sha256:84ecfe3df17454993b8dd485201566609ac6706a2eb22e3f402da2a39f9f6b5f"}, + {file = "griffe-0.36.1-py3-none-any.whl", hash = "sha256:859b653fcde0a0af0e841a0109bac2b63a2f683132ae1ec8dae5fa81e94617a0"}, + {file = "griffe-0.36.1.tar.gz", hash = "sha256:11df63f1c85f605c73e4485de70ec13784049695d228241b0b582364a20c0536"}, ] [package.dependencies] @@ -1020,6 +1020,16 @@ files = [ {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, @@ -1602,13 +1612,13 @@ testutils = ["gitpython (>3)"] [[package]] name = "pymdown-extensions" -version = "10.2" +version = "10.3" description = "Extension pack for Python Markdown." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pymdown_extensions-10.2-py3-none-any.whl", hash = "sha256:fbb86243db9a681602e3b869deef000211c55d0261015a5cc41d6f34d2afc57f"}, - {file = "pymdown_extensions-10.2.tar.gz", hash = "sha256:06042274876eb4267f12a389daf505eabaebc38bdca26725560c9afda5867549"}, + {file = "pymdown_extensions-10.3-py3-none-any.whl", hash = "sha256:77a82c621c58a83efc49a389159181d570e370fff9f810d3a4766a75fc678b66"}, + {file = "pymdown_extensions-10.3.tar.gz", hash = "sha256:94a0d8a03246712b64698af223848fd80aaf1ae4c4be29c8c61939b0467b5722"}, ] [package.dependencies] @@ -1692,13 +1702,13 @@ cp2110 = ["hidapi"] [[package]] name = "pytest" -version = "7.4.0" +version = "7.4.2" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-7.4.0-py3-none-any.whl", hash = "sha256:78bf16451a2eb8c7a2ea98e32dc119fd2aa758f1d5d66dbf0a59d69a3969df32"}, - {file = "pytest-7.4.0.tar.gz", hash = "sha256:b4bf8c45bd59934ed84001ad51e11b4ee40d40a1229d2c79f9c592b0a3f6bd8a"}, + {file = "pytest-7.4.2-py3-none-any.whl", hash = "sha256:1d881c6124e08ff0a1bb75ba3ec0bfd8b5354a01c194ddd5a0a870a48d99b002"}, + {file = "pytest-7.4.2.tar.gz", hash = "sha256:a766259cfab564a2ad52cb1aae1b881a75c3eb7e34ca3779697c23ed47c47069"}, ] [package.dependencies] @@ -1728,13 +1738,13 @@ six = ">=1.5" [[package]] name = "pytz" -version = "2023.3" +version = "2023.3.post1" description = "World timezone definitions, modern and historical" optional = false python-versions = "*" files = [ - {file = "pytz-2023.3-py2.py3-none-any.whl", hash = "sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb"}, - {file = "pytz-2023.3.tar.gz", hash = "sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588"}, + {file = "pytz-2023.3.post1-py2.py3-none-any.whl", hash = "sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7"}, + {file = "pytz-2023.3.post1.tar.gz", hash = "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b"}, ] [[package]] @@ -1979,108 +1989,108 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"] [[package]] name = "rpds-py" -version = "0.10.0" +version = "0.10.2" description = "Python bindings to Rust's persistent data structures (rpds)" optional = true python-versions = ">=3.8" files = [ - {file = "rpds_py-0.10.0-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:c1e0e9916301e3b3d970814b1439ca59487f0616d30f36a44cead66ee1748c31"}, - {file = "rpds_py-0.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8ce8caa29ebbdcde67e5fd652c811d34bc01f249dbc0d61e5cc4db05ae79a83b"}, - {file = "rpds_py-0.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad277f74b1c164f7248afa968700e410651eb858d7c160d109fb451dc45a2f09"}, - {file = "rpds_py-0.10.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8e1c68303ccf7fceb50fbab79064a2636119fd9aca121f28453709283dbca727"}, - {file = "rpds_py-0.10.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:780fcb855be29153901c67fc9c5633d48aebef21b90aa72812fa181d731c6b00"}, - {file = "rpds_py-0.10.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bbd7b24d108509a1b9b6679fcc1166a7dd031dbef1f3c2c73788f42e3ebb3beb"}, - {file = "rpds_py-0.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0700c2133ba203c4068aaecd6a59bda22e06a5e46255c9da23cbf68c6942215d"}, - {file = "rpds_py-0.10.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:576da63eae7809f375932bfcbca2cf20620a1915bf2fedce4b9cc8491eceefe3"}, - {file = "rpds_py-0.10.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:23750a9b8a329844ba1fe267ca456bb3184984da2880ed17ae641c5af8de3fef"}, - {file = "rpds_py-0.10.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:d08395595c42bcd82c3608762ce734504c6d025eef1c06f42326a6023a584186"}, - {file = "rpds_py-0.10.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1d7b7b71bcb82d8713c7c2e9c5f061415598af5938666beded20d81fa23e7640"}, - {file = "rpds_py-0.10.0-cp310-none-win32.whl", hash = "sha256:97f5811df21703446b42303475b8b855ee07d6ab6cdf8565eff115540624f25d"}, - {file = "rpds_py-0.10.0-cp310-none-win_amd64.whl", hash = "sha256:cdbed8f21204398f47de39b0a9b180d7e571f02dfb18bf5f1b618e238454b685"}, - {file = "rpds_py-0.10.0-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:7a3a3d3e4f1e3cd2a67b93a0b6ed0f2499e33f47cc568e3a0023e405abdc0ff1"}, - {file = "rpds_py-0.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fc72ae476732cdb7b2c1acb5af23b478b8a0d4b6fcf19b90dd150291e0d5b26b"}, - {file = "rpds_py-0.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c0583f69522732bdd79dca4cd3873e63a29acf4a299769c7541f2ca1e4dd4bc6"}, - {file = "rpds_py-0.10.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f8b9a7cd381970e64849070aca7c32d53ab7d96c66db6c2ef7aa23c6e803f514"}, - {file = "rpds_py-0.10.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0d292cabd7c8335bdd3237ded442480a249dbcdb4ddfac5218799364a01a0f5c"}, - {file = "rpds_py-0.10.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6903cdca64f1e301af9be424798328c1fe3b4b14aede35f04510989fc72f012"}, - {file = "rpds_py-0.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bed57543c99249ab3a4586ddc8786529fbc33309e5e8a1351802a06ca2baf4c2"}, - {file = "rpds_py-0.10.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:15932ec5f224b0e35764dc156514533a4fca52dcfda0dfbe462a1a22b37efd59"}, - {file = "rpds_py-0.10.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:eb2d59bc196e6d3b1827c7db06c1a898bfa0787c0574af398e65ccf2e97c0fbe"}, - {file = "rpds_py-0.10.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f99d74ddf9d3b6126b509e81865f89bd1283e3fc1b568b68cd7bd9dfa15583d7"}, - {file = "rpds_py-0.10.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f70bec8a14a692be6dbe7ce8aab303e88df891cbd4a39af091f90b6702e28055"}, - {file = "rpds_py-0.10.0-cp311-none-win32.whl", hash = "sha256:5f7487be65b9c2c510819e744e375bd41b929a97e5915c4852a82fbb085df62c"}, - {file = "rpds_py-0.10.0-cp311-none-win_amd64.whl", hash = "sha256:748e472345c3a82cfb462d0dff998a7bf43e621eed73374cb19f307e97e08a83"}, - {file = "rpds_py-0.10.0-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:d4639111e73997567343df6551da9dd90d66aece1b9fc26c786d328439488103"}, - {file = "rpds_py-0.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f4760e1b02173f4155203054f77a5dc0b4078de7645c922b208d28e7eb99f3e2"}, - {file = "rpds_py-0.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a6420a36975e0073acaeee44ead260c1f6ea56812cfc6c31ec00c1c48197173"}, - {file = "rpds_py-0.10.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:58fc4d66ee349a23dbf08c7e964120dc9027059566e29cf0ce6205d590ed7eca"}, - {file = "rpds_py-0.10.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:063411228b852fb2ed7485cf91f8e7d30893e69b0acb207ec349db04cccc8225"}, - {file = "rpds_py-0.10.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65af12f70355de29e1092f319f85a3467f4005e959ab65129cb697169ce94b86"}, - {file = "rpds_py-0.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:298e8b5d8087e0330aac211c85428c8761230ef46a1f2c516d6a2f67fb8803c5"}, - {file = "rpds_py-0.10.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5b9bf77008f2c55dabbd099fd3ac87009471d223a1c7ebea36873d39511b780a"}, - {file = "rpds_py-0.10.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c7853f27195598e550fe089f78f0732c66ee1d1f0eaae8ad081589a5a2f5d4af"}, - {file = "rpds_py-0.10.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:75dbfd41a61bc1fb0536bf7b1abf272dc115c53d4d77db770cd65d46d4520882"}, - {file = "rpds_py-0.10.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b25136212a3d064a8f0b9ebbb6c57094c5229e0de76d15c79b76feff26aeb7b8"}, - {file = "rpds_py-0.10.0-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:9affee8cb1ec453382c27eb9043378ab32f49cd4bc24a24275f5c39bf186c279"}, - {file = "rpds_py-0.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4d55528ef13af4b4e074d067977b1f61408602f53ae4537dccf42ba665c2c7bd"}, - {file = "rpds_py-0.10.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7865df1fb564092bcf46dac61b5def25342faf6352e4bc0e61a286e3fa26a3d"}, - {file = "rpds_py-0.10.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3f5cc8c7bc99d2bbcd704cef165ca7d155cd6464c86cbda8339026a42d219397"}, - {file = "rpds_py-0.10.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cbae50d352e4717ffc22c566afc2d0da744380e87ed44a144508e3fb9114a3f4"}, - {file = "rpds_py-0.10.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fccbf0cd3411719e4c9426755df90bf3449d9fc5a89f077f4a7f1abd4f70c910"}, - {file = "rpds_py-0.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78d10c431073dc6ebceed35ab22948a016cc2b5120963c13a41e38bdde4a7212"}, - {file = "rpds_py-0.10.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1b401e8b9aece651512e62c431181e6e83048a651698a727ea0eb0699e9f9b74"}, - {file = "rpds_py-0.10.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:7618a082c55cf038eede4a918c1001cc8a4411dfe508dc762659bcd48d8f4c6e"}, - {file = "rpds_py-0.10.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:b3226b246facae14909b465061ddcfa2dfeadb6a64f407f24300d42d69bcb1a1"}, - {file = "rpds_py-0.10.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:a8edd467551c1102dc0f5754ab55cd0703431cd3044edf8c8e7d9208d63fa453"}, - {file = "rpds_py-0.10.0-cp38-none-win32.whl", hash = "sha256:71333c22f7cf5f0480b59a0aef21f652cf9bbaa9679ad261b405b65a57511d1e"}, - {file = "rpds_py-0.10.0-cp38-none-win_amd64.whl", hash = "sha256:a8ab1adf04ae2d6d65835995218fd3f3eb644fe20655ca8ee233e2c7270ff53b"}, - {file = "rpds_py-0.10.0-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:87c93b25d538c433fb053da6228c6290117ba53ff6a537c133b0f2087948a582"}, - {file = "rpds_py-0.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e7996aed3f65667c6dcc8302a69368435a87c2364079a066750a2eac75ea01e"}, - {file = "rpds_py-0.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8856aa76839dc234d3469f1e270918ce6bec1d6a601eba928f45d68a15f04fc3"}, - {file = "rpds_py-0.10.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:00215f6a9058fbf84f9d47536902558eb61f180a6b2a0fa35338d06ceb9a2e5a"}, - {file = "rpds_py-0.10.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:23a059143c1393015c68936370cce11690f7294731904bdae47cc3e16d0b2474"}, - {file = "rpds_py-0.10.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3e5c26905aa651cc8c0ddc45e0e5dea2a1296f70bdc96af17aee9d0493280a17"}, - {file = "rpds_py-0.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c651847545422c8131660704c58606d841e228ed576c8f1666d98b3d318f89da"}, - {file = "rpds_py-0.10.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:80992eb20755701753e30a6952a96aa58f353d12a65ad3c9d48a8da5ec4690cf"}, - {file = "rpds_py-0.10.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ffcf18ad3edf1c170e27e88b10282a2c449aa0358659592462448d71b2000cfc"}, - {file = "rpds_py-0.10.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:08e08ccf5b10badb7d0a5c84829b914c6e1e1f3a716fdb2bf294e2bd01562775"}, - {file = "rpds_py-0.10.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7150b83b3e3ddaac81a8bb6a9b5f93117674a0e7a2b5a5b32ab31fdfea6df27f"}, - {file = "rpds_py-0.10.0-cp39-none-win32.whl", hash = "sha256:3455ecc46ea443b5f7d9c2f946ce4017745e017b0d0f8b99c92564eff97e97f5"}, - {file = "rpds_py-0.10.0-cp39-none-win_amd64.whl", hash = "sha256:afe6b5a04b2ab1aa89bad32ca47bf71358e7302a06fdfdad857389dca8fb5f04"}, - {file = "rpds_py-0.10.0-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:b1cb078f54af0abd835ca76f93a3152565b73be0f056264da45117d0adf5e99c"}, - {file = "rpds_py-0.10.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8e7e2b3577e97fa43c2c2b12a16139b2cedbd0770235d5179c0412b4794efd9b"}, - {file = "rpds_py-0.10.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae46a50d235f1631d9ec4670503f7b30405103034830bc13df29fd947207f795"}, - {file = "rpds_py-0.10.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f869e34d2326e417baee430ae998e91412cc8e7fdd83d979277a90a0e79a5b47"}, - {file = "rpds_py-0.10.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3d544a614055b131111bed6edfa1cb0fb082a7265761bcb03321f2dd7b5c6c48"}, - {file = "rpds_py-0.10.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ee9c2f6ca9774c2c24bbf7b23086264e6b5fa178201450535ec0859739e6f78d"}, - {file = "rpds_py-0.10.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2da4a8c6d465fde36cea7d54bf47b5cf089073452f0e47c8632ecb9dec23c07"}, - {file = "rpds_py-0.10.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ac00c41dd315d147b129976204839ca9de699d83519ff1272afbe4fb9d362d12"}, - {file = "rpds_py-0.10.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:0155c33af0676fc38e1107679be882077680ad1abb6303956b97259c3177e85e"}, - {file = "rpds_py-0.10.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:db6585b600b2e76e98131e0ac0e5195759082b51687ad0c94505970c90718f4a"}, - {file = "rpds_py-0.10.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:7b6975d3763d0952c111700c0634968419268e6bbc0b55fe71138987fa66f309"}, - {file = "rpds_py-0.10.0-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:6388e4e95a26717b94a05ced084e19da4d92aca883f392dffcf8e48c8e221a24"}, - {file = "rpds_py-0.10.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:18f87baa20e02e9277ad8960cd89b63c79c05caf106f4c959a9595c43f2a34a5"}, - {file = "rpds_py-0.10.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92f05fc7d832e970047662b3440b190d24ea04f8d3c760e33e7163b67308c878"}, - {file = "rpds_py-0.10.0-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:291c9ce3929a75b45ce8ddde2aa7694fc8449f2bc8f5bd93adf021efaae2d10b"}, - {file = "rpds_py-0.10.0-pp38-pypy38_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:861d25ae0985a1dd5297fee35f476b60c6029e2e6e19847d5b4d0a43a390b696"}, - {file = "rpds_py-0.10.0-pp38-pypy38_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:668d2b45d62c68c7a370ac3dce108ffda482b0a0f50abd8b4c604a813a59e08f"}, - {file = "rpds_py-0.10.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:344b89384c250ba6a4ce1786e04d01500e4dac0f4137ceebcaad12973c0ac0b3"}, - {file = "rpds_py-0.10.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:885e023e73ce09b11b89ab91fc60f35d80878d2c19d6213a32b42ff36543c291"}, - {file = "rpds_py-0.10.0-pp38-pypy38_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:841128a22e6ac04070a0f84776d07e9c38c4dcce8e28792a95e45fc621605517"}, - {file = "rpds_py-0.10.0-pp38-pypy38_pp73-musllinux_1_2_i686.whl", hash = "sha256:899b5e7e2d5a8bc92aa533c2d4e55e5ebba095c485568a5e4bedbc163421259a"}, - {file = "rpds_py-0.10.0-pp38-pypy38_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:e7947d9a6264c727a556541b1630296bbd5d0a05068d21c38dde8e7a1c703ef0"}, - {file = "rpds_py-0.10.0-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:4992266817169997854f81df7f6db7bdcda1609972d8ffd6919252f09ec3c0f6"}, - {file = "rpds_py-0.10.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:26d9fd624649a10e4610fab2bc820e215a184d193e47d0be7fe53c1c8f67f370"}, - {file = "rpds_py-0.10.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0028eb0967942d0d2891eae700ae1a27b7fd18604cfcb16a1ef486a790fee99e"}, - {file = "rpds_py-0.10.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f9e7e493ded7042712a374471203dd43ae3fff5b81e3de1a0513fa241af9fd41"}, - {file = "rpds_py-0.10.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2d68a8e8a3a816629283faf82358d8c93fe5bd974dd2704152394a3de4cec22a"}, - {file = "rpds_py-0.10.0-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d6d5f061f6a2aa55790b9e64a23dfd87b6664ab56e24cd06c78eb43986cb260b"}, - {file = "rpds_py-0.10.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c7c4266c1b61eb429e8aeb7d8ed6a3bfe6c890a1788b18dbec090c35c6b93fa"}, - {file = "rpds_py-0.10.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:80772e3bda6787510d9620bc0c7572be404a922f8ccdfd436bf6c3778119464c"}, - {file = "rpds_py-0.10.0-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:b98e75b21fc2ba5285aef8efaf34131d16af1c38df36bdca2f50634bea2d3060"}, - {file = "rpds_py-0.10.0-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:d63787f289944cc4bde518ad2b5e70a4f0d6e2ce76324635359c74c113fd188f"}, - {file = "rpds_py-0.10.0-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:872f3dcaa8bf2245944861d7311179d2c0c9b2aaa7d3b464d99a7c2e401f01fa"}, - {file = "rpds_py-0.10.0.tar.gz", hash = "sha256:e36d7369363d2707d5f68950a64c4e025991eb0177db01ccb6aa6facae48b69f"}, + {file = "rpds_py-0.10.2-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:9f00d54b18dd837f1431d66b076737deb7c29ce3ebb8412ceaf44d5e1954ac0c"}, + {file = "rpds_py-0.10.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f4d561f4728f825e3b793a53064b606ca0b6fc264f67d09e54af452aafc5b82"}, + {file = "rpds_py-0.10.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:013d6c784150d10236a74b4094a79d96a256b814457e388fc5a4ba9efe24c402"}, + {file = "rpds_py-0.10.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd1142d22fdb183a0fff66d79134bf644401437fed874f81066d314c67ee193c"}, + {file = "rpds_py-0.10.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a0536ed2b9297c75104e1a3da330828ba1b2639fa53b38d396f98bf7e3c68df"}, + {file = "rpds_py-0.10.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:41bd430b7b63aa802c02964e331ac0b177148fef5f807d2c90d05ce71a52b4d4"}, + {file = "rpds_py-0.10.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e8474f7233fe1949ce4e03bea698a600c2d5d6b51dab6d6e6336dbe69acf23e"}, + {file = "rpds_py-0.10.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d9d7efaad48b859053b90dedd69bc92f2095084251e732e4c57ac9726bcb1e64"}, + {file = "rpds_py-0.10.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5612b0b1de8d5114520094bd5fc3d04eb8af6f3e10d48ef05b7c8e77c1fd9545"}, + {file = "rpds_py-0.10.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5d5eaf988951f6ecb6854ca3300b87123599c711183c83da7ce39717a7cbdbce"}, + {file = "rpds_py-0.10.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:75c8766734ac0053e1d683567e65e85306c4ec62631b0591caeb287ac8f72e08"}, + {file = "rpds_py-0.10.2-cp310-none-win32.whl", hash = "sha256:8de9b88f0cbac73cfed34220d13c57849e62a7099a714b929142425e926d223a"}, + {file = "rpds_py-0.10.2-cp310-none-win_amd64.whl", hash = "sha256:2275f1a022e2383da5d2d101fe11ccdcbae799148c4b83260a4b9309fa3e1fc2"}, + {file = "rpds_py-0.10.2-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:dd91a7d7a9ce7f4983097c91ce211f3e5569cc21caa16f2692298a07e396f82b"}, + {file = "rpds_py-0.10.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e82b4a70cc67094f3f3fd77579702f48fcf1de7bdc67d79b8f1e24d089a6162c"}, + {file = "rpds_py-0.10.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e281b71922208e00886e4b7ffbfcf27874486364f177418ab676f102130e7ec9"}, + {file = "rpds_py-0.10.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b3eb1a0d2b6d232d1bcdfc3fcc5f7b004ab3fbd9203011a3172f051d4527c0b6"}, + {file = "rpds_py-0.10.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:02945ae38fd78efc40900f509890de84cfd5ffe2cd2939eeb3a8800dc68b87cb"}, + {file = "rpds_py-0.10.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ccfb77f6dc8abffa6f1c7e3975ed9070a41ce5fcc11154d2bead8c1baa940f09"}, + {file = "rpds_py-0.10.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af52078719209bef33e38131486fd784832dd8d1dc9b85f00a44f6e7437dd021"}, + {file = "rpds_py-0.10.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:56ba7c1100ed079527f2b995bf5486a2e557e6d5b733c52e8947476338815b69"}, + {file = "rpds_py-0.10.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:899b03a3be785a7e1ff84b237da71f0efa2f021512f147dd34ffdf7aa82cb678"}, + {file = "rpds_py-0.10.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:22e6de18f00583f06928cc8d0993104ecc62f7c6da6478db2255de89a30e45d1"}, + {file = "rpds_py-0.10.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:edd74b760a6bb950397e7a7bd2f38e6700f6525062650b1d77c6d851b82f02c2"}, + {file = "rpds_py-0.10.2-cp311-none-win32.whl", hash = "sha256:18909093944727e068ebfc92e2e6ed1c4fa44135507c1c0555213ce211c53214"}, + {file = "rpds_py-0.10.2-cp311-none-win_amd64.whl", hash = "sha256:9568764e72d85cf7855ca78b48e07ed1be47bf230e2cea8dabda3c95f660b0ff"}, + {file = "rpds_py-0.10.2-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:0fc625059b83695fbb4fc8b7a8b66fa94ff9c7b78c84fb9986cd53ff88a28d80"}, + {file = "rpds_py-0.10.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c86231c66e4f422e7c13ea6200bb4048b3016c8bfd11b4fd0dabd04d2c8e3501"}, + {file = "rpds_py-0.10.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56777c57246e048908b550af9b81b0ec9cf804fd47cb7502ccd93238bd6025c2"}, + {file = "rpds_py-0.10.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a4cb372e22e9c879bd9a9cc9b20b7c1fbf30a605ac953da45ecec05d8a6e1c77"}, + {file = "rpds_py-0.10.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa3b3a43dabc4cc57a7800f526cbe03f71c69121e21b863fdf497b59b462b163"}, + {file = "rpds_py-0.10.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:59d222086daa55421d599609b32d0ebe544e57654c4a0a1490c54a7ebaa67561"}, + {file = "rpds_py-0.10.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:529aab727f54a937085184e7436e1d0e19975cf10115eda12d37a683e4ee5342"}, + {file = "rpds_py-0.10.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43e9b1531d6a898bdf086acb75c41265c7ec4331267d7619148d407efc72bd24"}, + {file = "rpds_py-0.10.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c2772bb95062e3f9774140205cd65d8997e39620715486cf5f843cf4ad8f744c"}, + {file = "rpds_py-0.10.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ba1b28e44f611f3f2b436bd8290050a61db4b59a8e24be4465f44897936b3824"}, + {file = "rpds_py-0.10.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5aba767e64b494483ad60c4873bec78d16205a21f8247c99749bd990d9c846c2"}, + {file = "rpds_py-0.10.2-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:e1954f4b239d1a92081647eecfd51cbfd08ea16eb743b8af1cd0113258feea14"}, + {file = "rpds_py-0.10.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:de4a2fd524993578fe093044f291b4b24aab134390030b3b9b5f87fd41ab7e75"}, + {file = "rpds_py-0.10.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e69737bd56006a86fd5a78b2b85447580a6138c930a75eb9ef39fe03d90782b1"}, + {file = "rpds_py-0.10.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f40abbcc0a7d9a8a80870af839d317e6932533f98682aabd977add6c53beeb23"}, + {file = "rpds_py-0.10.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29ec8507664f94cc08457d98cfc41c3cdbddfa8952438e644177a29b04937876"}, + {file = "rpds_py-0.10.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bcde80aefe7054fad6277762fb7e9d35c72ea479a485ae1bb14629c640987b30"}, + {file = "rpds_py-0.10.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a65de5c02884760a14a58304fb6303f9ddfc582e630f385daea871e1bdb18686"}, + {file = "rpds_py-0.10.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e92e5817eb6bfed23aa5e45bfe30647b83602bdd6f9e25d63524d4e6258458b0"}, + {file = "rpds_py-0.10.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:2c8fc6c841ada60a86d29c9ebe2e8757c47eda6553f3596c560e59ca6e9b6fa1"}, + {file = "rpds_py-0.10.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:8557c807388e6617161fe51b1a4747ea8d1133f2d2ad8e79583439abebe58fbd"}, + {file = "rpds_py-0.10.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:00e97d43a36811b78fa9ad9d3329bf34f76a31e891a7031a2ac01450c9b168ab"}, + {file = "rpds_py-0.10.2-cp38-none-win32.whl", hash = "sha256:1ed3d5385d14be894e12a9033be989e012214a9811e7194849c94032ad69682a"}, + {file = "rpds_py-0.10.2-cp38-none-win_amd64.whl", hash = "sha256:02b4a2e28eb24dac4ef43dda4f6a6f7766e355179b143f7d0c76a1c5488a307b"}, + {file = "rpds_py-0.10.2-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:2a55631b93e47956fbc97d69ba2054a8c6a4016f9a3064ec4e031f5f1030cb90"}, + {file = "rpds_py-0.10.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2ffbf1b38c88d0466de542e91b08225d51782282512f8e2b11715126c41fda48"}, + {file = "rpds_py-0.10.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213f9ef5c02ec2f883c1075d25a873149daadbaea50d18d622e9db55ec9849c2"}, + {file = "rpds_py-0.10.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b00150a9a3fd0a8efaa90bc2696c105b04039d50763dd1c95a34c88c5966cb57"}, + {file = "rpds_py-0.10.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ab0f7aabdbce4a202e013083eeab71afdb85efa405dc4a06fea98cde81204675"}, + {file = "rpds_py-0.10.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2cd0c9fb5d40887500b4ed818770c68ab4fa6e0395d286f9704be6751b1b7d98"}, + {file = "rpds_py-0.10.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8578fc6c8bdd0201327503720fa581000b4bd3934abbf07e2628d1ad3de157d"}, + {file = "rpds_py-0.10.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2d27d08056fcd61ff47a0cd8407eff4d3e816c82cb6b9c6f0ce9a0ad49225f81"}, + {file = "rpds_py-0.10.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c8f6526df47953b07c45b95c4d1da6b9a0861c0e5da0271db96bb1d807825412"}, + {file = "rpds_py-0.10.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:177c033e467a66a054dd3a9534167234a3d0b2e41445807b13b626e01da25d92"}, + {file = "rpds_py-0.10.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9c74cbee9e532dc34371127f7686d6953e5153a1f22beab7f953d95ee4a0fe09"}, + {file = "rpds_py-0.10.2-cp39-none-win32.whl", hash = "sha256:05a1382905026bdd560f806c8c7c16e0f3e3fb359ba8868203ca6e5799884968"}, + {file = "rpds_py-0.10.2-cp39-none-win_amd64.whl", hash = "sha256:3fd503c27e7b7034128e30847ecdb4bff4ca5e60f29ad022a9f66ae8940d54ac"}, + {file = "rpds_py-0.10.2-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:4a96147791e49e84207dd1530109aa0e9eeaf1c8b7a59f150047fc0fcdf9bb64"}, + {file = "rpds_py-0.10.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:203eb1532d51591d32e8dfafd60b5d31347ea7278c8da02b4b550287f6abe28b"}, + {file = "rpds_py-0.10.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a2f416cdfe92f5fbb77177f5f3f7830059d1582db05f2c7119bf80069d1ab69b"}, + {file = "rpds_py-0.10.2-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b2660000e1a113869c86eb5cc07f3343467490f3cd9d0299f81da9ddae7137b7"}, + {file = "rpds_py-0.10.2-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1adb04e4b4e41bf30aaa77eeb169c1b9ba9e5010e2e6ce8d6c17e1446edc9b68"}, + {file = "rpds_py-0.10.2-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2bca97521ee786087f0c5ef318fef3eef0266a9c3deff88205523cf353af7394"}, + {file = "rpds_py-0.10.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4969592e3cdeefa4cbb15a26cec102cbd4a1d6e5b695fac9fa026e19741138c8"}, + {file = "rpds_py-0.10.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:df61f818edf7c8626bfa392f825860fb670b5f8336e238eb0ec7e2a5689cdded"}, + {file = "rpds_py-0.10.2-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:b589d93a60e78fe55d5bc76ee8c2bf945dbdbb7cd16044c53e0307604e448de1"}, + {file = "rpds_py-0.10.2-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:73da69e1f612c3e682e34dcb971272d90d6f27b2c99acff444ca455a89978574"}, + {file = "rpds_py-0.10.2-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:89438e8885a186c69fe31f7ef98bb2bf29688c466c3caf9060f404c0be89ae80"}, + {file = "rpds_py-0.10.2-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:c4ecc4e9a5d73a816cae36ee6b5d8b7a0c72013cae1e101406e832887c3dc2d8"}, + {file = "rpds_py-0.10.2-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:907b214da5d2fcff0b6ddb83de1333890ca92abaf4bbf8d9c61dc1b95c87fd6e"}, + {file = "rpds_py-0.10.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb44644371eaa29a3aba7b69b1862d0d56f073bb7585baa32e4271a71a91ee82"}, + {file = "rpds_py-0.10.2-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:80c3cf46511653f94dfe07c7c79ab105c4164d6e1dfcb35b7214fb9af53eaef4"}, + {file = "rpds_py-0.10.2-pp38-pypy38_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eaba0613c759ebf95988a84f766ca6b7432d55ce399194f95dde588ad1be0878"}, + {file = "rpds_py-0.10.2-pp38-pypy38_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0527c97dcd8bb983822ee31d3760187083fd3ba18ac4dd22cf5347c89d5628f4"}, + {file = "rpds_py-0.10.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9cdfd649011ce2d90cb0dd304c5aba1190fac0c266d19a9e2b96b81cfd150a09"}, + {file = "rpds_py-0.10.2-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:75eea40355a8690459c7291ce6c8ce39c27bd223675c7da6619f510c728feb97"}, + {file = "rpds_py-0.10.2-pp38-pypy38_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:4f1b804cfad04f862d6a84af9d1ad941b06f671878f0f7ecad6c92007d423de6"}, + {file = "rpds_py-0.10.2-pp38-pypy38_pp73-musllinux_1_2_i686.whl", hash = "sha256:bf77f9017fcfa1232f98598a637406e6c33982ccba8a5922339575c3e2b90ea5"}, + {file = "rpds_py-0.10.2-pp38-pypy38_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:46c4c550bf59ce05d6bff2c98053822549aaf9fbaf81103edea325e03350bca1"}, + {file = "rpds_py-0.10.2-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:46af4a742b90c7460e94214f923452c2c1d050a9da1d2b8d4c70cbc045e692b7"}, + {file = "rpds_py-0.10.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:2a86d246a160d98d820ee7d02dc18c923c228de095be362e57b9fd8970b2c4a1"}, + {file = "rpds_py-0.10.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae141c9017f8f473a6ee07a9425da021816a9f8c0683c2e5442f0ccf56b0fc62"}, + {file = "rpds_py-0.10.2-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e1147bc3d0dd1e549d991110d0a09557ec9f925dbc1ca62871fcdab2ec9d716b"}, + {file = "rpds_py-0.10.2-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fce7a8ee8d0f682c953c0188735d823f0fcb62779bf92cd6ba473a8e730e26ad"}, + {file = "rpds_py-0.10.2-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4c7f9d70f99e1fbcbf57c75328b80e1c0a7f6cad43e75efa90a97221be5efe15"}, + {file = "rpds_py-0.10.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b309908b6ff5ffbf6394818cb73b5a2a74073acee2c57fe8719046389aeff0d"}, + {file = "rpds_py-0.10.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3ff1f585a0fdc1415bd733b804f33d386064a308672249b14828130dd43e7c31"}, + {file = "rpds_py-0.10.2-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:0188b580c490bccb031e9b67e9e8c695a3c44ac5e06218b152361eca847317c3"}, + {file = "rpds_py-0.10.2-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:abe081453166e206e3a8c6d8ace57214c17b6d9477d7601ac14a365344dbc1f4"}, + {file = "rpds_py-0.10.2-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:9118de88c16947eaf5b92f749e65b0501ea69e7c2be7bd6aefc12551622360e1"}, + {file = "rpds_py-0.10.2.tar.gz", hash = "sha256:289073f68452b96e70990085324be7223944c7409973d13ddfe0eea1c1b5663b"}, ] [[package]] @@ -2099,19 +2109,19 @@ paramiko = "*" [[package]] name = "setuptools" -version = "68.1.2" +version = "68.2.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = true python-versions = ">=3.8" files = [ - {file = "setuptools-68.1.2-py3-none-any.whl", hash = "sha256:3d8083eed2d13afc9426f227b24fd1659489ec107c0e86cec2ffdde5c92e790b"}, - {file = "setuptools-68.1.2.tar.gz", hash = "sha256:3d4dfa6d95f1b101d695a6160a7626e15583af71a5f52176efa5d39a054d475d"}, + {file = "setuptools-68.2.0-py3-none-any.whl", hash = "sha256:af3d5949030c3f493f550876b2fd1dd5ec66689c4ee5d5344f009746f71fd5a8"}, + {file = "setuptools-68.2.0.tar.gz", hash = "sha256:00478ca80aeebeecb2f288d3206b0de568df5cd2b8fada1209843cc9a8d88a48"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5,<=7.1.2)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "six" @@ -2148,13 +2158,13 @@ files = [ [[package]] name = "soupsieve" -version = "2.4.1" +version = "2.5" description = "A modern CSS selector implementation for Beautiful Soup." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "soupsieve-2.4.1-py3-none-any.whl", hash = "sha256:1c1bfee6819544a3447586c889157365a27e10d88cde3ad3da0cf0ddf646feb8"}, - {file = "soupsieve-2.4.1.tar.gz", hash = "sha256:89d12b2d5dfcd2c9e8c22326da9d9aa9cb3dfab0a83a024f05704076ee8d35ea"}, + {file = "soupsieve-2.5-py3-none-any.whl", hash = "sha256:eaa337ff55a1579b6549dc679565eac1e3d000563bcb1c8ab0d0fefbc0c2cdc7"}, + {file = "soupsieve-2.5.tar.gz", hash = "sha256:5663d5a7b3bfaeee0bc4372e7fc48f9cff4940b3eec54a6451cc5299f1097690"}, ] [[package]] diff --git a/pyproject.toml b/pyproject.toml index 3f93198d..ff78e42e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "netutils" -version = "1.5.0" +version = "1.6.0" description = "Common helper functions useful in network automation." authors = ["Network to Code, LLC "] license = "Apache-2.0"