Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-ciscoconfparse for openSUSE:Factory checked in at 2023-01-04 17:53:34 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-ciscoconfparse (Old) and /work/SRC/openSUSE:Factory/.python-ciscoconfparse.new.1563 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-ciscoconfparse" Wed Jan 4 17:53:34 2023 rev:28 rq:1055824 version:1.7.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-ciscoconfparse/python-ciscoconfparse.changes 2022-12-08 16:52:23.899855826 +0100 +++ /work/SRC/openSUSE:Factory/.python-ciscoconfparse.new.1563/python-ciscoconfparse.changes 2023-01-04 17:53:50.858621871 +0100 @@ -1,0 +2,10 @@ +Wed Jan 4 14:04:22 UTC 2023 - Dirk Müller <dmuel...@suse.com> + +- update to 1.7.0: + * Add deprecat dependency + * Add more Makefile targets + * Makefile will successfully ping to internet or fail + * Update Makefile to delete poetry.lock file + * Correct 'make ping' logic and other tricky Makefile syntax + +------------------------------------------------------------------- Old: ---- ciscoconfparse-1.6.53.tar.gz New: ---- ciscoconfparse-1.7.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-ciscoconfparse.spec ++++++ --- /var/tmp/diff_new_pack.02GLkX/_old 2023-01-04 17:53:51.318624582 +0100 +++ /var/tmp/diff_new_pack.02GLkX/_new 2023-01-04 17:53:51.334624677 +0100 @@ -1,7 +1,7 @@ # # spec file for package python-ciscoconfparse # -# Copyright (c) 2022 SUSE LLC +# Copyright (c) 2023 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -19,7 +19,7 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} %bcond_without python2 Name: python-ciscoconfparse -Version: 1.6.53 +Version: 1.7.0 Release: 0 Summary: Library for parsing, querying and modifying Cisco IOS-style configurations License: GPL-3.0-or-later ++++++ ciscoconfparse-1.6.53.tar.gz -> ciscoconfparse-1.7.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ciscoconfparse-1.6.53/CHANGES.md new/ciscoconfparse-1.7.0/CHANGES.md --- old/ciscoconfparse-1.6.53/CHANGES.md 2022-11-09 20:24:50.707943000 +0100 +++ new/ciscoconfparse-1.7.0/CHANGES.md 2023-01-02 15:07:57.459834600 +0100 @@ -2,15 +2,36 @@ - Released: Not released - Summary: + - Insert something here + +## Version: 1.7.1 + +- Released: 2023-01-02 +- Summary: + - Add deprecat dependency + - Add more Makefile targets + - Makefile will successfully ping to internet or fail + - Update Makefile to delete poetry.lock file + - Correct 'make ping' logic and other tricky Makefile syntax + +## Version: 1.7.0 + +- Released: 2023-01-02 +- Summary: + - Add deprecat dependency + - Add more Makefile targets + - Makefile will successfully ping to internet or fail - Update Makefile to delete poetry.lock file + - Correct 'make ping' logic and other tricky Makefile syntax ## Version: 1.6.53 -- Released: 2022-11-09 +- Released: 2022-11-18 - Summary: - Reformat pyproject.toml to be most compatible with 'pip install' - Several internal project-level optimizations... + - git changes committed on 2022-11-09... somehow 1.6.53 wasn't pushed to pypi on 9-Nov-2022. It was pushed to pypi on 18-Nov-2022 ## Version: 1.6.52 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ciscoconfparse-1.6.53/Makefile new/ciscoconfparse-1.7.0/Makefile --- old/ciscoconfparse-1.6.53/Makefile 2022-11-18 12:15:49.277552400 +0100 +++ new/ciscoconfparse-1.7.0/Makefile 2023-01-02 15:56:14.612502800 +0100 @@ -1,7 +1,20 @@ DOCHOST ?= $(shell bash -c 'read -p "documentation host: " dochost; echo $$dochost') # VERSION detection: # Ref -> https://stackoverflow.com/a/71592061/667301 -VERSION := $(shell grep version pyproject.toml | tr -s ' ' | tr -d "'" | tr -d '"' | cut -d' ' -f3) +export VERSION := $(shell grep version pyproject.toml | tr -s ' ' | tr -d "'" | tr -d '"' | cut -d' ' -f3) + +# We MUST escape Makefile dollar signs as $$foo +export PING_OUTPUT := $(shell perl -e '@output = qx/ping -q -W0.5 -c1 4.2.2.2/; $$alloutput = join "", @output; if ( $$alloutput =~ /\s0\sreceived/ ) { print "failure"; } else { print "success"; }') + +export NUMBER_OF_CCP_TESTS := $(shell grep "def " tests/test*py | wc -l) + +# Makefile color codes... +# ref -> https://stackoverflow.com/a/5947802/667301 +COL_GREEN=\033[0;32m +COL_CYAN=\033[0;36m +COL_YELLOW=\033[0;33m +COL_RED=\033[0;31m +COL_END=\033[0;0m .DEFAULT_GOAL := test @@ -9,7 +22,7 @@ # Ref -> https://packaging.python.org/en/latest/guides/making-a-pypi-friendly-readme/ .PHONY: pypi-packaging pypi-packaging: - @echo ">> building ciscoconfparse pypi artifacts (wheel and tar.gz)" + @echo "$(COL_GREEN)>> building ciscoconfparse pypi artifacts (wheel and tar.gz)$(COL_END)" pip install -U setuptools>=58.0.0 pip install -U wheel>=0.37.1 pip install -U twine>=4.0.1 @@ -19,7 +32,7 @@ .PHONY: pypi pypi: - @echo ">> uploading ciscoconfparse pypi artifacts to pypi" + @echo "$(COL_CYAN)>> uploading ciscoconfparse pypi artifacts to pypi$(COL_END)" make clean make pypi-packaging poetry lock --no-update @@ -27,6 +40,9 @@ # twine is the simplest pypi package uploader... python -m twine upload dist/* +.PHONY: readme +readme: + .PHONY: bump-version-patch bump-version-patch: $(shell python dev_tools/git_helper.py -I patch) @@ -35,45 +51,51 @@ bump-version-minor: $(shell python dev_tools/git_helper.py -I minor) + .PHONY: repo-push repo-push: - @echo ">> git push (w/o force) ciscoconfparse local main branch to github" - #git remote remove origin - #git remote add origin "g...@github.com:mpenning/ciscoconfparse" - #git push g...@github.com:mpenning/ciscoconfparse.git - #git push origin +main - $(shell python dev_tools/git_helper.py -P ciscoconfparse --push) + @echo "$(COL_GREEN)>> git push and merge (w/o force) to ciscoconfparse main branch to github$(COL_END)" + make ping + -git checkout master || git checkout main # Add dash to ignore checkout fails + # Now the main branch is checked out... + THIS_BRANCH=$(shell git branch --show-current) # assign 'main' to $THIS_BRANCH + git merge @{-1} # merge the previous branch into main... + git push origin $(THIS_BRANCH) # git push to origin / $THIS_BRANCH + git checkout @{-1} # checkout the previous branch... + .PHONY: repo-push-force repo-push-force: - @echo ">> git push (w/ force) ciscoconfparse local main branch to github" - #git remote remove origin - #git remote add origin "g...@github.com:mpenning/ciscoconfparse" - #git push --force-with-lease g...@github.com:mpenning/ciscoconfparse.git - #git push --force-with-lease origin +main - $(shell python dev_tools/git_helper.py -P ciscoconfparse --push --force) + @echo "$(COL_GREEN)>> git push and merge (w/ force) ciscoconfparse local main branch to github$(COL_END)" + make ping + -git checkout master || git checkout main # Add dash to ignore checkout fails + # Now the main branch is checked out... + THIS_BRANCH=$(shell git branch --show-current) # assign 'main' to $THIS_BRANCH + git merge @{-1} # merge the previous branch into main... + git push --force-with-lease origin $(THIS_BRANCH) # force push to origin / $THIS_BRANCH + git checkout @{-1} # checkout the previous branch... .PHONY: repo-push-tag repo-push-tag: - @echo ">> git push (w/ local tag) ciscoconfparse local main branch to github" - #make repo-push - $(shell python dev_tools/git_helper.py -P ciscoconfparse --push --tag) + @echo "$(COL_GREEN)>> git push (w/ local tag) ciscoconfparse local main branch to github$(COL_END)" + git push origin +main + git push --tags origin +main .PHONY: repo-push-tag-force repo-push-tag-force: - @echo ">> git push (w/ local tag and w/ force) ciscoconfparse local main branch to github" - #make repo-push-force - $(shell python dev_tools/git_helper.py -P ciscoconfparse --push --tag --force) + @echo "$(COL_GREEN)>> git push (w/ local tag and w/ force) ciscoconfparse local main branch to github$(COL_END)" + git push --force-with-lease origin +main + git push --force-with-lease --tags origin +main .PHONY: pylama pylama: - @echo ">> running pylama against ciscoconfparse" + @echo "$(COL_GREEN)>> running pylama against ciscoconfparse$(COL_END)" # Good usability info here -> https://pythonspeed.com/articles/pylint/ pylama --ignore=E501,E301,E265,E266 ciscoconfparse/*py | less -XR .PHONY: pylint pylint: - @echo ">> running pylint against ciscoconfparse" + @echo "$(COL_GREEN)>> running pylint against ciscoconfparse$(COL_END)" # Good usability info here -> https://pythonspeed.com/articles/pylint/ pylint --rcfile=./utils/pylintrc --ignore-patterns='^build|^dist|utils/pylintrc|README.rst|CHANGES|LICENSE|MANIFEST.in|Makefile|TODO' --output-format=colorized * | less -XR @@ -118,24 +140,24 @@ .PHONY: pip pip: - @echo ">> Upgrading pip to the latest version" - pip install -U pip + @echo "$(COL_GREEN)>> Upgrading pip to the latest version$(COL_END)" + make ping + pip install -U pip>=22.2.0 .PHONY: dep dep: - @echo ">> installing all ciscoconfparse prod dependencies" - make pip - pip install -U pip>=22.2.0 - pip install -U dnspython==2.1.0 # Previously version 1.14.0 + @echo "$(COL_GREEN)>> installing all ciscoconfparse prod dependencies$(COL_END)" + pip install -U dnspython==1.15.0 # Previously version 1.14.0 pip install -U passlib==1.7.4 pip install -U loguru==0.6.0 pip install -U toml>=0.10.2 + pip install -U deprecat>=2.1.1 .PHONY: dev dev: - @echo ">> installing all prod and development ciscoconfparse dependencies" + @echo "$(COL_GREEN)>> installing all prod and development ciscoconfparse dependencies$(COL_END)" + make pip make dep - pip install -U pip>=22.2.0 pip install -U virtualenv pip install -U virtualenvwrapper>=4.8.0 pip install -U pss @@ -156,14 +178,35 @@ pip install -U invoke>=1.7.0 pip install -U ipaddr>=2.2.0 +.PHONY: rm-timestamp +rm-timestamp: + @echo "$(COL_GREEN)>> delete .pip_dependency if older than a day$(COL_END)" + #delete .pip_dependency if older than a day + $(shell find .pip_dependency -mtime +1 -exec rm {} \;) + +.PHONY: timestamp +timestamp: + @echo "$(COL_GREEN)>> delete .pip_dependency if older than a day$(COL_END)" + $(shell touch .pip_dependency) + +.PHONY: ping +ping: + @echo "$(COL_GREEN)>> ping to ensure internet connectivity$(COL_END)" + @if [ "$${PING_OUTPUT}" = 'success' ]; then return 0; else return 1; fi .PHONY: test test: + @echo "$(COL_GREEN)>> running unit tests$(COL_END)" + $(shell touch .pip_dependency) + make timestamp + make dep + #make ping make clean cd tests && ./runtests.sh .PHONY: clean clean: + @echo "$(COL_GREEN)>> cleaning the repo$(COL_END)" # Delete bogus files... see https://stackoverflow.com/a/73992288/667301 perl -e 'unlink( grep { /^=\d*\.*\d*/ && !-d } glob( "*" ) );' find ./* -name '*.pyc' -exec rm {} \; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ciscoconfparse-1.6.53/PKG-INFO new/ciscoconfparse-1.7.0/PKG-INFO --- old/ciscoconfparse-1.6.53/PKG-INFO 1970-01-01 01:00:00.000000000 +0100 +++ new/ciscoconfparse-1.7.0/PKG-INFO 1970-01-01 01:00:00.000000000 +0100 @@ -1,7 +1,7 @@ Metadata-Version: 2.1 Name: ciscoconfparse -Version: 1.6.53 -Summary: Parse, Audit, Query, Build, and Modify Cisco IOS-style and JunOS-style configurations +Version: 1.7.0 +Summary: Parse, Audit, Query, Build, and Modify Cisco IOS-style and JunOS-style configs License: GPL-3.0-only Keywords: Parse,audit,query,modify,Cisco IOS,Cisco,NXOS,ASA,Juniper Author: Mike Pennington @@ -29,6 +29,7 @@ Classifier: Topic :: System :: Networking :: Monitoring Requires-Dist: Sphinx (==4.3.0) Requires-Dist: black (>=20.8b1) +Requires-Dist: deprecat (>=2.1.1) Requires-Dist: dnspython (>=2.1.0,<3.0.0) Requires-Dist: loguru (==0.6.0) Requires-Dist: packaging (>21.0) @@ -52,7 +53,7 @@ ciscoconfparse ============== -[![Github unittest workflow][4]][5] [![Code Health][37]][38] [![Version][2]][3] [![Downloads][6]][7] [![License][8]][9] +[![Github unittest workflow][4]][5] [![Code Health][37]][38] [![git commits][41]][42] [![Repo Code Grade][43]][44] [![Version][2]][3] [![Downloads][6]][7] [![License][8]][9] Introduction: What is ciscoconfparse? @@ -180,6 +181,11 @@ cd ciscoconfparse/ python -m pip install . +Github Star History +------------------- + +[![Github Star History Chart][40]][40] + Other Resources --------------- @@ -265,4 +271,9 @@ [37]: https://snyk.io/advisor/python/ciscoconfparse/badge.svg [38]: https://snyk.io/advisor/python/ciscoconfparse [39]: https://www.reddit.com/r/Python/ + [40]: https://api.star-history.com/svg?repos=mpenning/ciscoconfparse&type=Date + [41]: https://img.shields.io/github/commit-activity/m/mpenning/ciscoconfparse + [42]: https://img.shields.io/github/commit-activity/m/mpenning/ciscoconfparse + [43]: https://www.codefactor.io/Content/badges/B.svg + [44]: https://www.codefactor.io/repository/github/mpenning/ciscoconfparse/ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ciscoconfparse-1.6.53/README.md new/ciscoconfparse-1.7.0/README.md --- old/ciscoconfparse-1.6.53/README.md 2022-10-22 22:21:23.743468000 +0200 +++ new/ciscoconfparse-1.7.0/README.md 2022-12-17 12:00:56.507440600 +0100 @@ -1,7 +1,7 @@ ciscoconfparse ============== -[![Github unittest workflow][4]][5] [![Code Health][37]][38] [![Version][2]][3] [![Downloads][6]][7] [![License][8]][9] +[![Github unittest workflow][4]][5] [![Code Health][37]][38] [![git commits][41]][42] [![Repo Code Grade][43]][44] [![Version][2]][3] [![Downloads][6]][7] [![License][8]][9] Introduction: What is ciscoconfparse? @@ -129,6 +129,11 @@ cd ciscoconfparse/ python -m pip install . +Github Star History +------------------- + +[![Github Star History Chart][40]][40] + Other Resources --------------- @@ -214,3 +219,8 @@ [37]: https://snyk.io/advisor/python/ciscoconfparse/badge.svg [38]: https://snyk.io/advisor/python/ciscoconfparse [39]: https://www.reddit.com/r/Python/ + [40]: https://api.star-history.com/svg?repos=mpenning/ciscoconfparse&type=Date + [41]: https://img.shields.io/github/commit-activity/m/mpenning/ciscoconfparse + [42]: https://img.shields.io/github/commit-activity/m/mpenning/ciscoconfparse + [43]: https://www.codefactor.io/Content/badges/B.svg + [44]: https://www.codefactor.io/repository/github/mpenning/ciscoconfparse/ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ciscoconfparse-1.6.53/ciscoconfparse/ccp_util.py new/ciscoconfparse-1.7.0/ciscoconfparse/ccp_util.py --- old/ciscoconfparse-1.6.53/ciscoconfparse/ccp_util.py 2022-10-22 22:21:23.743468000 +0200 +++ new/ciscoconfparse-1.7.0/ciscoconfparse/ccp_util.py 2023-01-02 16:14:56.554239700 +0100 @@ -40,6 +40,8 @@ from dns.resolver import Resolver from dns import reversename, query, zone +from deprecat import deprecat + from loguru import logger from ciscoconfparse.protocol_values import ASA_TCP_PORTS, ASA_UDP_PORTS @@ -855,6 +857,7 @@ else: raise ValueError("type(%s) is not supported" % arg) + if isinstance(arg, str): # Removing string length checks in 1.6.29... there are too many # options such as IPv4Obj("111.111.111.111 255.255.255.255") @@ -1191,10 +1194,7 @@ """ Fix github issue #203... build a `_prefixlen` attribute... """ - if self.version == 4: - return IPV4_MAX_PREFIXLEN - else: - return IPV6_MAX_PREFIXLEN + return IPV4_MAX_PREFIXLEN # do NOT wrap with @logger.catch(...) # On IPv4Obj() @@ -1625,7 +1625,10 @@ except Exception as ee: masklen = IPV6_MAX_PREFIXLEN - netmask_int = 2**(128 - masklen) - 1 + assert isinstance(masklen, int) and masklen <= 128 + # If we have to derive the netmask as a long hex string, + # calculate the netmask from the masklen as follows... + netmask_int = (2**128 - 1) - (2**(128 - masklen) - 1) netmask = str(IPv6Address(netmask_int)) elif isinstance(arg, int): @@ -1637,7 +1640,7 @@ elif isinstance(arg, IPv6Obj): addr = str(arg.ip) netmask = str(arg.netmask) - masklen = arg.masklen + masklen = int(arg.masklen) else: raise AddressValueError("IPv6Obj(arg='%s')" % (arg)) @@ -1883,10 +1886,7 @@ """ Fix github issue #203... build a `_prefixlen` attribute... """ - if self.version == 4: - return IPV4_MAX_PREFIXLEN - else: - return IPV6_MAX_PREFIXLEN + return IPV6_MAX_PREFIXLEN # On IPv6Obj() @property @@ -2331,7 +2331,7 @@ start = time.time() if (query_type == "A") or (query_type == "AAAA"): try: - answer = rr.query(input_str, query_type) + answer = query(input_str, query_type) duration = time.time() - start for result in answer: response = DNSResponse( @@ -2477,14 +2477,21 @@ @logger.catch(reraise=True) +@deprecat(reason="dns_lookup() is obsolete; use dns_query() instead. dns_lookup() will be removed", version = '1.7.0') def dns_lookup(input_str, timeout=3, server="", record_type="A"): """Perform a simple DNS lookup, return results in a dictionary""" + assert isinstance(input_str, str) + assert isinstance(timeout, int) + assert isinstance(server, str) + assert isinstance(record_type, str) + rr = Resolver() rr.timeout = float(timeout) rr.lifetime = float(timeout) - if server: + if server != "": rr.nameservers = [server] - dns_session = rr.resolve(input_str, record_type) + #dns_session = rr.resolve(input_str, record_type) + dns_session = rr.query(input_str, record_type) responses = list() for rdata in dns_session: responses.append(str(rdata)) @@ -2517,6 +2524,7 @@ @logger.catch(reraise=True) +@deprecat(reason="dns6_lookup() is obsolete; use dns_query() instead. dns6_lookup() will be removed", version = '1.7.0') def dns6_lookup(input_str, timeout=3, server=""): """Perform a simple DNS lookup, return results in a dictionary""" rr = Resolver() @@ -2571,6 +2579,7 @@ return (input_addr, ipaddr_family) @logger.catch(reraise=True) +@deprecat(reason="reverse_dns_lookup() is obsolete; use dns_query() instead. reverse_dns_lookup() will be removed", version = '1.7.0') def reverse_dns_lookup(input_str, timeout=3.0, server="4.2.2.2", proto="udp"): """Perform a simple reverse DNS lookup on an IPv4 or IPv6 address; return results in a python dictionary""" assert isinstance(proto, str) and (proto=="udp" or proto=="tcp") @@ -2587,35 +2596,16 @@ else: raise ValueError() - rr = Resolver() - rr.nameservers = [] - - # Append valid addresses to rr.nameservers... - for candidate_server_addr in server.split(","): - addr, addr_family = check_valid_ipaddress(input_addr=candidate_server_addr) - rr.nameservers.append(addr) - - #rr = rr.resolve_address(ipaddr=input_str, tcp=tcp_flag, rdtype="PTR", lifetime=float(timeout)) - #rr = rr.resolve_address(ipaddr=input_str, tcp=tcp_flag, lifetime=float(timeout)) - - records = [] - retval = {} - try: - records = rr.resolve_address(ipaddr=input_str, tcp=tcp_flag, lifetime=float(timeout)) - retval = { - "name": records[0].to_text(), # this key was called "addrs" - "lookup": input_str, - "error": "", - "addrs": [input_str], - } - except DNSException as e: - retval = { - "name": "", # this key was called "addrs" - "lookup": input_str, - "error": repr(e), - "addrs": [input_str], - } + raw_result = dns_query(input_str, query_type="PTR", server=server, timeout=timeout) + assert isinstance(raw_result, set) + assert len(raw_result)>=1 + tmp = raw_result.pop() + assert isinstance(tmp, DNSResponse) + if tmp.has_error is True: + retval = {'addrs': [input_str], 'error': str(tmp.error_str), 'name': tmp.result_str} + else: + retval = {'addrs': [input_str], 'error': '', 'name': tmp.result_str} return retval diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ciscoconfparse-1.6.53/ciscoconfparse/ciscoconfparse.py new/ciscoconfparse-1.7.0/ciscoconfparse/ciscoconfparse.py --- old/ciscoconfparse-1.6.53/ciscoconfparse/ciscoconfparse.py 2022-11-09 16:20:29.818929000 +0100 +++ new/ciscoconfparse-1.7.0/ciscoconfparse/ciscoconfparse.py 2022-12-12 22:59:40.827519700 +0100 @@ -83,6 +83,7 @@ # Not using ccp_re yet... still a work in progress # from ciscoconfparse.ccp_util import ccp_re +from deprecat import deprecat from loguru import logger import toml @@ -437,6 +438,8 @@ encoding : str A string holding the coding type. Default is `locale.getpreferredencoding()` + + Returns ------- :class:`~ciscoconfparse.CiscoConfParse` @@ -503,7 +506,9 @@ assert self.syntax in ALL_VALID_SYNTAX + # Read the configuration lines and detect invalid inputs... config = self.get_config_lines(config=config, logger=logger) + valid_syntax = copy.copy(set(ALL_VALID_SYNTAX)) # add exceptions for brace-delimited syntax... @@ -589,38 +594,54 @@ @logger.catch(reraise=True) def get_config_lines(self, config=None, logger=None, linesplit_rgx=r"\r*\n+"): """ - Enforce rules - If config is a str, assume it's a filepath. If config is a list, assume it's a router config. + If the config parameter is a str, assume it's a filepath and read the config. If the config parameter is a list, assume it's a list of text config commands. Return the list of text configuration commands or raise an error. """ config_lines = None - # config string - assume a filename... open file return lines... - if self.debug > 0: - logger.debug("parsing config from '%s'" % config) + if config is None: + raise ValueError("config='%s' is unsupported. `config` must be either a python string, patlib.Path, or list" % config) - try: - assert isinstance(config, (str, pathlib.Path,)) - assert os.path.isfile(config) is True - with open(config, **self.openargs) as fh: - text = fh.read() - rgx = re.compile(linesplit_rgx) - config_lines = rgx.split(text) + elif isinstance(config, list) or isinstance(config, Iterator): + # Here we assume that `config` is a list of text config lines... + # + # config list of text lines... + assert len(config) > 0, "FATAL - there is no configuration stored in the list()" + if self.debug > 0: + logger.debug("parsing config stored in this list: '%s'" % config) + config_lines = config return config_lines - except (OSError or FileNotFoundError): - error = "CiscoConfParse could not open() the filepath '%s'" % config - logger.critical(error) - raise OSError - - except AssertionError: - # Allow list / iterator config to fall through the next logic below - pass + elif isinstance(config, (str, pathlib.Path,)) and os.path.isfile(config) is True: - if isinstance(config, (Iterator, list,)): - config_lines = config - return config_lines + # config string - assume a filename... open file return lines... + if self.debug > 0: + logger.debug("parsing config from the filepath named '%s'" % config) + + try: + with open(file=config, **self.openargs) as fh: + text = fh.read() + rgx = re.compile(linesplit_rgx) + config_lines = rgx.split(text) + return config_lines + + except OSError: + error = "CiscoConfParse could not open() the filepath named '%s'" % config + logger.critical(error) + raise OSError("FATAL - could not open() the config filepath named '{}'".format(config)) + + except Exception as eee: + error = "FATAL - {}".format(str(eee)) + logger.critical(error) + raise OSError(error) + + elif isinstance(config, (str, pathlib.Path,)) and os.path.isfile(config) is False: + if self.debug > 0: + logger.debug("filepath not found - '%s'" % config) + error = """FATAL - Attempted to open(file='{0}', mode='r', encoding='{1}'); however, file filepath named:"{0}" does not exist.""".format(config, self.openargs['encoding']) + raise ValueError(error) else: - raise ValueError("config='%s' is an unexpected type()" % config) + raise ValueError("config='%s' is an unexpected type(). `config` must be either a python string, patlib.Path, or list" % config) ######################################################################### # This method is on CiscoConfParse() @@ -643,17 +664,17 @@ # This method is on CiscoConfParse() @property def ioscfg(self): - """A list containing all text configuration statements""" + """Return a list containing all text configuration statements""" ## I keep ioscfg to emulate legacy ciscoconfparse behavior return [ii.text for ii in self.ConfigObjs] # This method is on CiscoConfParse() @property def objs(self): - """An alias to the ``ConfigObjs`` attribute""" + """CiscoConfParse().objs is an alias for the CiscoConfParse().ConfigObjs property; it returns a ConfigList() of config-line objects.""" if self.ConfigObjs is None: err_txt = ("ConfigObjs is set to None. ConfigObjs should be a " - "list of text {} config strings".format(self.syntax)) + "ConfigList() of configuration-line objects".format(self.syntax)) logger.error(err_txt) raise ValueError(err_txt) return self.ConfigObjs @@ -661,7 +682,7 @@ # This method is on CiscoConfParse() @logger.catch(reraise=True) def atomic(self): - """Call :func:`~ciscoconfparse.CiscoConfParse.atomic` to manually fix + """Use :func:`~ciscoconfparse.CiscoConfParse.atomic` to manually fix up ``ConfigObjs`` relationships after modifying a parsed configuration. This method is slow; try to batch calls to :func:`~ciscoconfparse.CiscoConfParse.atomic()` if @@ -714,112 +735,6 @@ """ self.atomic() # atomic() calls self.ConfigObjs._bootstrap_from_text() - def _parse_line_braces_DEPRECATED(self, line_txt): - """ - """ - assert isinstance(line_txt, str) - child_indent = 0 - this_line_indent = 0 - - junos_re_str = r"""^ - (?:\s* - (?P<braces_close_left>\})*(?P<line1>.*?)(?P<braces_open_right>\{)*;* - |(?P<line2>[^\{\}]*?)(?P<braces_open_left>\{)(?P<condition2>.*?)(?P<braces_close_right>\});*\s* - |(?P<line3>[^\{\}]*?);*\s* - )$ - """ - line_re = re.compile(junos_re_str, re.VERBOSE) - comment_re = re.compile(r'^\s*(?P<delimiter>[{0}]+)(?P<comment>[^{0}]*)$'.format(re.escape(self.comment_delimiter))) - - mm = line_re.search(line_txt.strip()) - nn = comment_re.search(line_txt.strip()) - - if nn is not None: - results = nn.groupdict() - return (this_line_indent, child_indent, results.get('delimiter') + results.get('comment', '')) - - elif mm is not None: - results = mm.groupdict() - - # } line1 { foo bar this } { - braces_close_left = bool(results.get('braces_close_left', '')) - braces_open_right = bool(results.get('braces_open_right', '')) - - # line2 - braces_open_left = bool(results.get('braces_open_left', '')) - braces_close_right = bool(results.get('braces_close_right', '')) - - # line3 - line1_str = results.get('line1', '') - line3_str = results.get('line3', '') - - if braces_close_left and braces_open_right: - # Based off line1 - # } elseif { bar baz } { - this_line_indent -= 1 - child_indent += 0 - retval = results.get('line1', None) - return (this_line_indent, child_indent, retval) - - elif bool(line1_str) and (braces_close_left is False) and (braces_open_right is False): - # Based off line1: - # address 1.1.1.1 - this_line_indent -= 0 - child_indent += 0 - retval = results.get('line1', '').strip() - # Strip empty braces here - retval = re.sub(r'\s*\{\s*\}\s*', '', retval) - return (this_line_indent, child_indent, retval) - - elif (line1_str == '') and (braces_close_left is False) and (braces_open_right is False): - # Based off line1: - # return empty string - this_line_indent -= 0 - child_indent += 0 - return (this_line_indent, child_indent, '') - - elif braces_open_left and braces_close_right: - # Based off line2 - # this { bar baz } - this_line_indent -= 0 - child_indent += 0 - line = results.get('line2', None) or '' - condition = results.get('condition2', None) or '' - if condition.strip() == '': - retval = line - else: - retval = line + " {" + condition + " }" - return (this_line_indent, child_indent, retval) - - elif braces_close_left: - # Based off line1 - # } - this_line_indent -= 1 - child_indent -= 1 - return (this_line_indent, child_indent, '') - - elif braces_open_right: - # Based off line1 - # this that foo { - this_line_indent -= 0 - child_indent += 1 - line = results.get('line1', None) or '' - return (this_line_indent, child_indent, line) - - elif (line3_str != '') and (line3_str is not None): - this_line_indent += 0 - child_indent += 0 - return (this_line_indent, child_indent, '') - - else: - raise ValueError('Cannot parse {} match:"{}"'.format( - self.syntax, line_txt)) - - else: - raise ValueError('Cannot parse {}:"{}"'.format(self.syntax, - line_txt)) - - # This method is on CiscoConfParse() @logger.catch(reraise=True) def convert_terraform_to_ios(self, input_list, stop_width=4, quotes=False, comment_delimiter="#"): @@ -3083,6 +2998,7 @@ # This method is on CiscoConfParse() @logger.catch(reraise=True) + @deprecat(reason="req_cfgspec_all_diff() is obsolete; use HDiff() instead. req_cfgspec_all_diff() will be removed", version = '1.7.0') def req_cfgspec_all_diff(self, cfgspec, ignore_ws=False): """ req_cfgspec_all_diff takes a list of required configuration lines, @@ -3151,6 +3067,7 @@ # This method is on CiscoConfParse() @logger.catch(reraise=True) + @deprecat(reason="req_cfgspec_excl_diff() is obsolete; use HDiff() instead. req_cfgspec_excl_diff() will be removed", version = '1.7.0') def req_cfgspec_excl_diff(self, linespec, uncfgspec, cfgspec): r""" req_cfgspec_excl_diff accepts a linespec, an unconfig spec, and @@ -3339,6 +3256,7 @@ # This method is on CiscoConfParse() @logger.catch(reraise=True) + @deprecat(reason="sync_diff() is obsolete; use HDiff() instead. sync_diff() will be removed", version = '1.7.0') def sync_diff( self, cfgspec, @@ -3410,6 +3328,10 @@ deprecation_warn_str = "`sync_diff()` will be deprecated and removed in future versions." warnings.warn(deprecation_warn_str, DeprecationWarning) + assert isinstance(cfgspec, (list, tuple, Iterator)), "FATAL - `cfgspec` requires a python Iterable, typically a `list()`." + assert isinstance(linespec, str) and len(linespec) > 0, "FATAL - `linespec` requires a python string." + assert isinstance(unconfspec, str) and len(unconfspec) > 0, "FATAL - `unconfspec` requires a python string." + tmp = self._find_line_OBJ(linespec) if uncfgspec is None: uncfgspec = linespec diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ciscoconfparse-1.6.53/pyproject.toml new/ciscoconfparse-1.7.0/pyproject.toml --- old/ciscoconfparse-1.6.53/pyproject.toml 2022-11-18 11:42:29.383412000 +0100 +++ new/ciscoconfparse-1.7.0/pyproject.toml 2023-01-02 16:23:02.461910500 +0100 @@ -20,8 +20,8 @@ [tool.poetry] name = "ciscoconfparse" -version = "1.6.53" -description = "Parse, Audit, Query, Build, and Modify Cisco IOS-style and JunOS-style configurations" +version = "1.7.0" +description = "Parse, Audit, Query, Build, and Modify Cisco IOS-style and JunOS-style configs" license = "GPL-3.0-only" authors = ["Mike Pennington <m...@pennington.net>"] readme = "README.md" @@ -75,6 +75,7 @@ dnspython = "^2.1.0" loguru = "0.6.0" toml = ">=0.10.2" +deprecat = ">=2.1.1" [tool.poetry.urls] source = "https://github.com/mpenning/ciscoconfparse" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ciscoconfparse-1.6.53/setup.py new/ciscoconfparse-1.7.0/setup.py --- old/ciscoconfparse-1.6.53/setup.py 1970-01-01 01:00:00.000000000 +0100 +++ new/ciscoconfparse-1.7.0/setup.py 1970-01-01 01:00:00.000000000 +0100 @@ -10,6 +10,7 @@ install_requires = \ ['Sphinx==4.3.0', 'black>=20.8b1', + 'deprecat>=2.1.1', 'dnspython>=2.1.0,<3.0.0', 'loguru==0.6.0', 'packaging>21.0', @@ -27,9 +28,9 @@ setup_kwargs = { 'name': 'ciscoconfparse', - 'version': '1.6.53', - 'description': 'Parse, Audit, Query, Build, and Modify Cisco IOS-style and JunOS-style configurations', - 'long_description': 'ciscoconfparse\n==============\n\n[![Github unittest workflow][4]][5] [![Code Health][37]][38] [![Version][2]][3] [![Downloads][6]][7] [![License][8]][9]\n\n\nIntroduction: What is ciscoconfparse?\n-------------------------------------\n\nShort answer: ciscoconfparse is a [Python][10] library\nthat helps you quickly answer questions like these about your\nconfigurations:\n\n- What interfaces are shutdown?\n- Which interfaces are in trunk mode?\n- What address and subnet mask is assigned to each interface?\n- Which interfaces are missing a critical command?\n- Is this configuration missing a standard config line?\n\nIt can help you:\n\n- Audit existing router / switch / firewall / wlc configurations\n- Modify existing configurations\n- Build new configurations\n\nSpeaking generally, the library examines an IOS-style config and breaks\nit into a set of linked parent / child relationships. You can perform\ncomplex queries about these relationships.\n\n[![Cisco IOS config: Parent / child][11]][11]\n\nUsage\n-----\n\nThe following code will parse a configuration stored in\n\\\'exampleswitch.conf\\\' and select interfaces that are shutdown.\n\n```python\nfrom ciscoconfparse import CiscoConfParse\n\nparse = CiscoConfParse(\'exampleswitch.conf\', syntax=\'ios\')\n\nfor intf_obj in parse.find_objects_w_child(\'^interface\', \'^\\s+shutdown\'):\n print("Shutdown: " + intf_obj.text)\n```\n\nThe next example will find the IP address assigned to interfaces.\n\n```python\nfrom ciscoconfparse import CiscoConfParse\n\nparse = CiscoConfParse(\'exampleswitch.conf\', syntax=\'ios\')\n\nfor intf_obj in parse.find_objects(\'^interface\'):\n\n intf_name = intf_obj.re_match_typed(\'^interface\\s+(\\S.+?)$\')\n\n # Search children of all interfaces for a regex match and return\n # the value matched in regex match group 1. If there is no match,\n # return a default value: \'\'\n intf_ip_addr = intf_obj.re_match_iter_typed(\n r\'ip\\sa ddress\\s(\\d+\\.\\d+\\.\\d+\\.\\d+)\\s\', result_type=str,\n group=1, default=\'\')\n print("{0}: {1}".format(intf_name, intf_ip_addr))\n```\n\nWhat if we don\\\'t use Cisco?\n----------------------------\n\nDon\\\'t let that stop you.\n\nAs of CiscoConfParse 1.2.4, you can parse [brace-delimited configurations][13] into a Cisco IOS style (see [Github Issue \\#17][14]), which means that CiscoConfParse can parse these configurations:\n\n- Juniper Networks Junos\n- Palo Alto Networks Firewall configurations\n- F5 Networks configurations\n\nCiscoConfParse also handles anything that has a Cisco IOS style of configuration, which includes:\n\n- Cisco IOS, Cisco Nexus, Cisco IOS-XR, Cisco IOS-XE, Aironet OS, Cisco ASA, Cisco CatOS\n- Arista EOS\n- Brocade\n- HP Switches\n- Force 10 Switches\n- Dell PowerConnect Switches\n- Extreme Networks\n- Enterasys\n- Screenos\n\nDocs\n----\n\n- The latest copy of the docs are [archived on the web][15]\n- There is also a [CiscoConfParse Tuto rial][16]\n\nEditing the Package\n-------------------\n\n- `git clone https://github.com/mpenning/ciscoconfparse`\n- `cd ciscoconfparse`\n- `git checkout -b develop`\n- Add / modify / delete on the `develop` branch\n- `make test`\n- If tests run clean, `git commit` all the pending changes on the `develop` branch\n- (as required) Edit the version number in [pyproject.toml][12]\n- `git checkout main`\n- `git merge develop`\n- `make test`\n- `make repo-push`\n- `make pypi`\n\nPre-requisites\n--------------\n\n[The ciscoconfparse python package][3] requires Python versions 3.7+ (note: Python version 3.7.0 has a bug - ref [Github issue \\#117][18], but version 3.7.1 works); the OS should not matter.\n\nInstallation and Downloads\n--------------------------\n\n- Use `poetry` for Python3.x\\... :\n\n python -m pip install ciscoconfparse\n\nIf you\\\'re interested in the source, you can always pull from the [github repo][17]:\n\n- Download from [the github r epo][17]: :\n\n git clone git://github.com/mpenning/ciscoconfparse\n cd ciscoconfparse/\n python -m pip install .\n\nOther Resources\n---------------\n\n- [Dive into Python3](http://www.diveintopython3.net/) is a good way to learn Python\n- [Team CYMRU][30] has a [Secure IOS Template][29], which is especially useful for external-facing routers / switches\n- [Cisco\\\'s Guide to hardening IOS devices][31]\n- [Center for Internet Security Benchmarks][32] (An email address, cookies, and javascript are required)\n\nBug Tracker and Support\n-----------------------\n\n- Please report any suggestions, bug reports, or annoyances with a [github bug report][24].\n- If you\\\'re having problems with general python issues, consider searching for a solution on [Stack Overflow][33]. If you can\\\'t find a solution for your problem or need more help, you can [ask on Stack Overflow][34] or [reddit/r/Python][39].\n- If you\\\'re having problems with your Cisco devices, you can contact:\n - [Cisco TAC][28]\n - [reddit/r/Cisco][35]\n - [reddit/r/networking][36]\n - [NetworkEngineering.se][23]\n\nUnit-Tests\n----------\n\nThe project\\\'s [test workflow][1] checks ciscoconfparse on Python versions 3.6 and higher, as well as a [pypy JIT][22] executable.\n\nClick the image below for details; the current build status is: [![Github unittest status][4]][5]\n\nLicense and Copyright\n---------------------\n\n[ciscoconfparse][3] is licensed [GPLv3][21]\n\n- Copyright (C) 2021-2022 David Michael Pennington\n- Copyright (C) 2020-2021 David Michael Pennington at Cisco Systems (post-acquisition: Cisco acquired ThousandEyes)\n- Copyright (C) 2019 David Michael Pennington at ThousandEyes\n- Copyright (C) 2012-2019 David Michael Pennington at Samsung Data Services\n- Copyright (C) 2011-2012 David Michael Pennington at Dell Computer Corp\n- Copyright (C) 2007-2011 David Michael Pennington\n\nThe word \\"Cisco\\" is a registered trademark of [Cisco Systems][27].\n\nAutho r\n------\n\n[ciscoconfparse][3] was written by [David Michael Pennington][25] (mike \\[\\~at\\~\\] pennington \\[.dot.\\] net).\n\n\n [1]: https://github.com/mpenning/ciscoconfparse/tree/master/.github/workflows\n [2]: https://img.shields.io/pypi/v/ciscoconfparse.svg\n [3]: https://pypi.python.org/pypi/ciscoconfparse/\n [4]: https://github.com/mpenning/ciscoconfparse/actions/workflows/tests.yml/badge.svg\n [5]: https://github.com/mpenning/ciscoconfparse/actions/workflows/tests.yml\n [6]: https://pepy.tech/badge/ciscoconfparse\n [7]: https://pepy.tech/project/ciscoconfparse\n [8]: http://img.shields.io/badge/license-GPLv3-blue.svg\n [9]: https://www.gnu.org/copyleft/gpl.html\n [10]: https://www.python.org\n [11]: https://raw.githubusercontent.com/mpenning/ciscoconfparse/master/sphinx-doc/_static/ciscoconfparse_overview_75pct.png\n [12]: https://github.com/mpenning/ciscoconfparse/blob/main/pyproject.toml\n [13]: https://github.com/mpenning/ciscoconfparse/blob/master/conf igs/sample_01.junos\n [14]: https://github.com/mpenning/ciscoconfparse/issues/17\n [15]: http://www.pennington.net/py/ciscoconfparse/\n [16]: http://pennington.net/tutorial/ciscoconfparse/ccp_tutorial.html\n [17]: https://github.com/mpenning/ciscoconfparse\n [18]: https://github.com/mpenning/ciscoconfparse/issues/117\n [19]: https://github.com/mpenning/ciscoconfparse/issues/13\n [20]: https://github.com/CrackerJackMack/\n [21]: http://www.gnu.org/licenses/gpl-3.0.html\n [22]: https://pypy.org\n [23]: https://networkengineering.stackexchange.com/\n [24]: https://github.com/mpenning/ciscoconfparse/issues/new/choose\n [25]: https://github.com/mpenning\n [26]: https://github.com/muir\n [27]: https://www.cisco.com/\n [28]: https://www.cisco.com/go/support\n [29]: https://www.cymru.com/Documents/secure-ios-template.html\n [30]: https://team-cymru.com/company/\n [31]: http://www.cisco.com/c/en/us/support/docs/ip/access-lists/13608-21.html\n [32]: https://learn.cisecurity .org/benchmarks\n [33]: https://stackoverflow.com\n [34]: http://stackoverflow.com/questions/ask\n [35]: https://www.reddit.com/r/Cisco/\n [36]: https://www.reddit.com/r/networking\n [37]: https://snyk.io/advisor/python/ciscoconfparse/badge.svg\n [38]: https://snyk.io/advisor/python/ciscoconfparse\n [39]: https://www.reddit.com/r/Python/\n', + 'version': '1.7.0', + 'description': 'Parse, Audit, Query, Build, and Modify Cisco IOS-style and JunOS-style configs', + 'long_description': 'ciscoconfparse\n==============\n\n[![Github unittest workflow][4]][5] [![Code Health][37]][38] [![git commits][41]][42] [![Repo Code Grade][43]][44] [![Version][2]][3] [![Downloads][6]][7] [![License][8]][9]\n\n\nIntroduction: What is ciscoconfparse?\n-------------------------------------\n\nShort answer: ciscoconfparse is a [Python][10] library\nthat helps you quickly answer questions like these about your\nconfigurations:\n\n- What interfaces are shutdown?\n- Which interfaces are in trunk mode?\n- What address and subnet mask is assigned to each interface?\n- Which interfaces are missing a critical command?\n- Is this configuration missing a standard config line?\n\nIt can help you:\n\n- Audit existing router / switch / firewall / wlc configurations\n- Modify existing configurations\n- Build new configurations\n\nSpeaking generally, the library examines an IOS-style config and breaks\nit into a set of linked parent / child relationships. You can perform\n complex queries about these relationships.\n\n[![Cisco IOS config: Parent / child][11]][11]\n\nUsage\n-----\n\nThe following code will parse a configuration stored in\n\\\'exampleswitch.conf\\\' and select interfaces that are shutdown.\n\n```python\nfrom ciscoconfparse import CiscoConfParse\n\nparse = CiscoConfParse(\'exampleswitch.conf\', syntax=\'ios\')\n\nfor intf_obj in parse.find_objects_w_child(\'^interface\', \'^\\s+shutdown\'):\n print("Shutdown: " + intf_obj.text)\n```\n\nThe next example will find the IP address assigned to interfaces.\n\n```python\nfrom ciscoconfparse import CiscoConfParse\n\nparse = CiscoConfParse(\'exampleswitch.conf\', syntax=\'ios\')\n\nfor intf_obj in parse.find_objects(\'^interface\'):\n\n intf_name = intf_obj.re_match_typed(\'^interface\\s+(\\S.+?)$\')\n\n # Search children of all interfaces for a regex match and return\n # the value matched in regex match group 1. If there is no match,\n # return a default value: \'\'\n intf_ip_ addr = intf_obj.re_match_iter_typed(\n r\'ip\\saddress\\s(\\d+\\.\\d+\\.\\d+\\.\\d+)\\s\', result_type=str,\n group=1, default=\'\')\n print("{0}: {1}".format(intf_name, intf_ip_addr))\n```\n\nWhat if we don\\\'t use Cisco?\n----------------------------\n\nDon\\\'t let that stop you.\n\nAs of CiscoConfParse 1.2.4, you can parse [brace-delimited configurations][13] into a Cisco IOS style (see [Github Issue \\#17][14]), which means that CiscoConfParse can parse these configurations:\n\n- Juniper Networks Junos\n- Palo Alto Networks Firewall configurations\n- F5 Networks configurations\n\nCiscoConfParse also handles anything that has a Cisco IOS style of configuration, which includes:\n\n- Cisco IOS, Cisco Nexus, Cisco IOS-XR, Cisco IOS-XE, Aironet OS, Cisco ASA, Cisco CatOS\n- Arista EOS\n- Brocade\n- HP Switches\n- Force 10 Switches\n- Dell PowerConnect Switches\n- Extreme Networks\n- Enterasys\n- Screenos\n\nDocs\n----\n\n- The latest copy of the docs are [archived on the web][15]\n- There is also a [CiscoConfParse Tutorial][16]\n\nEditing the Package\n-------------------\n\n- `git clone https://github.com/mpenning/ciscoconfparse`\n- `cd ciscoconfparse`\n- `git checkout -b develop`\n- Add / modify / delete on the `develop` branch\n- `make test`\n- If tests run clean, `git commit` all the pending changes on the `develop` branch\n- (as required) Edit the version number in [pyproject.toml][12]\n- `git checkout main`\n- `git merge develop`\n- `make test`\n- `make repo-push`\n- `make pypi`\n\nPre-requisites\n--------------\n\n[The ciscoconfparse python package][3] requires Python versions 3.7+ (note: Python version 3.7.0 has a bug - ref [Github issue \\#117][18], but version 3.7.1 works); the OS should not matter.\n\nInstallation and Downloads\n--------------------------\n\n- Use `poetry` for Python3.x\\... :\n\n python -m pip install ciscoconfparse\n\nIf you\\\'re interested in the source, you can always pull from the [github repo][17]:\n\n- Download from [the github repo][17]: :\n\n git clone git://github.com/mpenning/ciscoconfparse\n cd ciscoconfparse/\n python -m pip install .\n\nGithub Star History\n-------------------\n\n[![Github Star History Chart][40]][40]\n\nOther Resources\n---------------\n\n- [Dive into Python3](http://www.diveintopython3.net/) is a good way to learn Python\n- [Team CYMRU][30] has a [Secure IOS Template][29], which is especially useful for external-facing routers / switches\n- [Cisco\\\'s Guide to hardening IOS devices][31]\n- [Center for Internet Security Benchmarks][32] (An email address, cookies, and javascript are required)\n\nBug Tracker and Support\n-----------------------\n\n- Please report any suggestions, bug reports, or annoyances with a [github bug report][24].\n- If you\\\'re having problems with general python issues, consider searching for a solution on [Stack Overflow][33]. If you can\\\'t find a solution for your problem or ne ed more help, you can [ask on Stack Overflow][34] or [reddit/r/Python][39].\n- If you\\\'re having problems with your Cisco devices, you can contact:\n - [Cisco TAC][28]\n - [reddit/r/Cisco][35]\n - [reddit/r/networking][36]\n - [NetworkEngineering.se][23]\n\nUnit-Tests\n----------\n\nThe project\\\'s [test workflow][1] checks ciscoconfparse on Python versions 3.6 and higher, as well as a [pypy JIT][22] executable.\n\nClick the image below for details; the current build status is: [![Github unittest status][4]][5]\n\nLicense and Copyright\n---------------------\n\n[ciscoconfparse][3] is licensed [GPLv3][21]\n\n- Copyright (C) 2021-2022 David Michael Pennington\n- Copyright (C) 2020-2021 David Michael Pennington at Cisco Systems (post-acquisition: Cisco acquired ThousandEyes)\n- Copyright (C) 2019 David Michael Pennington at ThousandEyes\n- Copyright (C) 2012-2019 David Michael Pennington at Samsung Data Services\n- Copyright (C) 2011-2012 David Michael Pennington at Dell Compute r Corp\n- Copyright (C) 2007-2011 David Michael Pennington\n\nThe word \\"Cisco\\" is a registered trademark of [Cisco Systems][27].\n\nAuthor\n------\n\n[ciscoconfparse][3] was written by [David Michael Pennington][25] (mike \\[\\~at\\~\\] pennington \\[.dot.\\] net).\n\n\n [1]: https://github.com/mpenning/ciscoconfparse/tree/master/.github/workflows\n [2]: https://img.shields.io/pypi/v/ciscoconfparse.svg\n [3]: https://pypi.python.org/pypi/ciscoconfparse/\n [4]: https://github.com/mpenning/ciscoconfparse/actions/workflows/tests.yml/badge.svg\n [5]: https://github.com/mpenning/ciscoconfparse/actions/workflows/tests.yml\n [6]: https://pepy.tech/badge/ciscoconfparse\n [7]: https://pepy.tech/project/ciscoconfparse\n [8]: http://img.shields.io/badge/license-GPLv3-blue.svg\n [9]: https://www.gnu.org/copyleft/gpl.html\n [10]: https://www.python.org\n [11]: https://raw.githubusercontent.com/mpenning/ciscoconfparse/master/sphinx-doc/_static/ciscoconfparse_overview_75pct.png\n [ 12]: https://github.com/mpenning/ciscoconfparse/blob/main/pyproject.toml\n [13]: https://github.com/mpenning/ciscoconfparse/blob/master/configs/sample_01.junos\n [14]: https://github.com/mpenning/ciscoconfparse/issues/17\n [15]: http://www.pennington.net/py/ciscoconfparse/\n [16]: http://pennington.net/tutorial/ciscoconfparse/ccp_tutorial.html\n [17]: https://github.com/mpenning/ciscoconfparse\n [18]: https://github.com/mpenning/ciscoconfparse/issues/117\n [19]: https://github.com/mpenning/ciscoconfparse/issues/13\n [20]: https://github.com/CrackerJackMack/\n [21]: http://www.gnu.org/licenses/gpl-3.0.html\n [22]: https://pypy.org\n [23]: https://networkengineering.stackexchange.com/\n [24]: https://github.com/mpenning/ciscoconfparse/issues/new/choose\n [25]: https://github.com/mpenning\n [26]: https://github.com/muir\n [27]: https://www.cisco.com/\n [28]: https://www.cisco.com/go/support\n [29]: https://www.cymru.com/Documents/secure-ios-template.html\n [30]: https ://team-cymru.com/company/\n [31]: http://www.cisco.com/c/en/us/support/docs/ip/access-lists/13608-21.html\n [32]: https://learn.cisecurity.org/benchmarks\n [33]: https://stackoverflow.com\n [34]: http://stackoverflow.com/questions/ask\n [35]: https://www.reddit.com/r/Cisco/\n [36]: https://www.reddit.com/r/networking\n [37]: https://snyk.io/advisor/python/ciscoconfparse/badge.svg\n [38]: https://snyk.io/advisor/python/ciscoconfparse\n [39]: https://www.reddit.com/r/Python/\n [40]: https://api.star-history.com/svg?repos=mpenning/ciscoconfparse&type=Date\n [41]: https://img.shields.io/github/commit-activity/m/mpenning/ciscoconfparse\n [42]: https://img.shields.io/github/commit-activity/m/mpenning/ciscoconfparse\n [43]: https://www.codefactor.io/Content/badges/B.svg\n [44]: https://www.codefactor.io/repository/github/mpenning/ciscoconfparse/\n', 'author': 'Mike Pennington', 'author_email': 'm...@pennington.net', 'maintainer': 'None',