From ed313c90f42c378a55b67f0228bbcb101549f4f0 Mon Sep 17 00:00:00 2001 From: jathanism Date: Tue, 30 Mar 2021 21:46:12 -0700 Subject: [PATCH 1/7] v2.0.0 initial pass - Everything converted from setup.py to pyproject.toml - Black everything - Docstrings and fixes - Unit tests using pytest now --- .hgignore | 7 - .hgtags | 6 - cidrize.py | 276 ++++++++++------ poetry.lock | 738 ++++++++++++++++++++++++++++++++++++++++++ pyproject.toml | 105 ++++++ scripts/cidr | 7 - tests/test_cidrize.py | 175 +++++----- 7 files changed, 1119 insertions(+), 195 deletions(-) delete mode 100644 .hgignore delete mode 100644 .hgtags mode change 100644 => 100755 cidrize.py create mode 100644 poetry.lock create mode 100644 pyproject.toml delete mode 100755 scripts/cidr diff --git a/.hgignore b/.hgignore deleted file mode 100644 index 89bc138..0000000 --- a/.hgignore +++ /dev/null @@ -1,7 +0,0 @@ -syntax:glob - -*.pyc -.*swp -build -dist -MANIFEST diff --git a/.hgtags b/.hgtags deleted file mode 100644 index b8fa8cc..0000000 --- a/.hgtags +++ /dev/null @@ -1,6 +0,0 @@ -97cd906c965de1790e6c22acb7bbf5dadb45b731 0.2 -97cd906c965de1790e6c22acb7bbf5dadb45b731 0.2 -bfbe14707b53a33fdee4c851407c8d411a5e0e58 0.2 -15560dfdb041af8c2fd4f355405a89e1d8dd72ce 0.3 -f5a6f43eb97087dc35c0a69b7b822988bbf53e26 0.3.1 -9122308fd7ce5ded39ce63beb21f92e47d6119ea 0.4 diff --git a/cidrize.py b/cidrize.py old mode 100644 new mode 100755 index fed2fdf..cb1122e --- a/cidrize.py +++ b/cidrize.py @@ -1,21 +1,19 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- -from __future__ import print_function - -__author__ = 'Jathan McCollum' -__email__ = 'jathan@gmail.com' -__version__ = '0.7.0' """ -Intelligently parse IPv4/IPv6 addresses, CIDRs, ranges, and wildcard matches to -attempt return a valid list of IP addresses. It's smart enough to fix bad -network boundaries for you. +IP address parsing for humans. + +Cidrize takes IP address inputs that people tend to use in practice, validates +them, and converts them to objects. + +It will intelligently parse IPv4/IPv6 addresses, CIDRs, ranges, and wildcard +matches to attempt return a valid list of IP addresses. It's smart enough to fix +bad network boundaries for you. -The cidrize() function is the public interface. The module may also be run +The ``cidrize()`` function is the public interface. The module may also be run interactively for debugging purposes. """ -# stdlib import itertools import logging import os @@ -23,46 +21,104 @@ import socket import sys -# pypi -from netaddr import (AddrFormatError, IPAddress, IPGlob, IPNetwork, IPRange, - IPSet, spanning_cidr) +from netaddr import ( + AddrFormatError, + IPAddress, + IPGlob, + IPNetwork, + IPRange, + IPSet, + spanning_cidr, +) + # Globals -EVERYTHING = ['internet at large', '*', 'all', 'any', 'internet', '0.0.0.0', - '0.0.0.0/0', '0.0.0.0-255.255.255.255'] -MAX_RANGE_LEN = 16384 # This is a /18 +EVERYTHING = [ + "internet at large", + "*", + "all", + "any", + "internet", + "0.0.0.0", + "0.0.0.0/0", + "0.0.0.0-255.255.255.255", +] +# IPRange objects larger than MAX_RANGE_LEN will always be strict. +# This is an IPv4 /18 +MAX_RANGE_LEN = 16384 # Setup logging -DEBUG = os.getenv('DEBUG', False) +DEBUG = os.getenv("DEBUG") LOG_LEVEL = logging.INFO LOG_FORMAT = "%(asctime)s [%(levelname)s]: %(message)s" if DEBUG: LOG_LEVEL = logging.DEBUG logging.basicConfig(level=LOG_LEVEL, format=LOG_FORMAT) -log = logging +log = logging.getLogger(__name__) +# # Pre-compiled re patterns. You know, for speed! -cidr_re = re.compile(r"\d+\.\d+\.\d+\.\d+(?:\/\d+)?$") -range_re = re.compile(r"\d+\.\d+\.\d+\.\d+\-\d+\.\d+\.\d+\.\d+$") -range6_re = re.compile(r"[0-9a-fA-F]+:[0-9A-Fa-f:.]+\-[0-9a-fA-F]+:[0-9A-Fa-f:.]+$") -glob_re = re.compile(r"\d+\.\d+\.\d+\.\*$") -bracket_re = re.compile(r"(.*?)\.(\d+)[\[\{\(](.*)[\)\}\]]$") # parses '1.2.3.4[5-9]' or '1.2.3.[57]' -hyphen_re = re.compile(r"(.*?)\.(\d+)\-(\d+)$") -hostname_re = re.compile(r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?)', re.IGNORECASE) +# + +# Old-fashioned CIDR notation (192.168.1.0/24) +RE_CIDR = re.compile(r"\d+\.\d+\.\d+\.\d+(?:\/\d+)?$") + +# 1.2.3.118-1.2.3.121 range style +RE_RANGE = re.compile(r"\d+\.\d+\.\d+\.\d+\-\d+\.\d+\.\d+\.\d+$") + +# 2001::14-2002::1.2.3.121 range style +RE_RANGE6 = re.compile( + r"[0-9a-fA-F]+:[0-9A-Fa-f:.]+\-[0-9a-fA-F]+:[0-9A-Fa-f:.]+$" +) + +# 1.2.3.* glob style +RE_GLOB = re.compile(r"\d+\.\d+\.\d+\.\*$") + +# 1.2.3.4[5-9] or 1.2.3.[49] bracket style as a last resort +RE_BRACKET = re.compile(r"(.*?)\.(\d+)[\[\{\(](.*)[\)\}\]]$") + +# 1.2.3.4-70 hyphen style +RE_HYPHEN = re.compile(r"(.*?)\.(\d+)\-(\d+)$") + +# Hostnames (we're assuming first char is alpha) +RE_HOSTNAME = re.compile( + r"(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?)", + re.IGNORECASE, +) # Exports -__all__ = ('cidrize', 'CidrizeError', 'dump', 'normalize_address', - 'optimize_network_usage', 'parse_range', 'is_ipv6') +__all__ = ( + "cidrize", + "CidrizeError", + "dump", + "normalize_address", + "optimize_network_usage", + "parse_range", + "is_ipv6", +) # Exceptions -class CidrizeError(AddrFormatError): pass -class NotCIDRStyleError(CidrizeError): pass -class NotRangeStyleError(CidrizeError): pass -class NotGlobStyleError(CidrizeError): pass -class NotBracketStyleError(CidrizeError): pass +class CidrizeError(AddrFormatError): + """Generic Cidrize error.""" + + +class NotCIDRStyle(CidrizeError): + """Not CIDR style?""" + + +class NotRangeStyle(CidrizeError): + """Not range style?""" + + +class NotGlobStyle(CidrizeError): + """Not glob style?""" + + +class NotBracketStyle(CidrizeError): + """Not bracket style?""" # Functions @@ -79,28 +135,31 @@ def parse_brackets(text): :param text: The string to parse. """ - match = bracket_re.match(text) + + match = RE_BRACKET.match(text) + if match is None: return None parts = match.groups() + # '1.2.3.4[5-9] style - log.debug('parse_brackets() parts: %r' % (parts,)) + log.debug("parse_brackets() parts: %r", (parts,)) if len(parts) == 3: prefix, subnet, enders = parts - network = '.'.join((prefix, subnet)) + network = ".".join((prefix, subnet)) # '1.2.3.[5-9] style elif len(parts) == 2: prefix, enders = parts - network = prefix + '.' + network = prefix + "." else: - raise NotBracketStyleError("Bracketed style not parseable: '%s'" % text) + raise NotBracketStyle("Bracketed style not parseable: '%s'" % text) # Split hyphenated [x-y] - if '-' in enders: - first, last = enders.split('-') + if "-" in enders: + first, last = enders.split("-") # Get first/last from [xy] - This really only works with single # digits @@ -113,6 +172,7 @@ def parse_brackets(text): return IPRange(network + first, network + last) + def parse_hyphen(text): """ Parses a hyphen in the last octet, e.g. '1.2.3.4-70' @@ -120,16 +180,17 @@ def parse_hyphen(text): :param text: The string to parse """ - match = hyphen_re.match(text) + match = RE_HYPHEN.match(text) if match is None: return None parts = match.groups() prefix, start, finish = parts - network = prefix + '.' + network = prefix + "." return IPRange(network + start, network + finish) + def parse_range(ipstr): """ Given a hyphenated range of IPs, return an IPRange object. @@ -137,13 +198,14 @@ def parse_range(ipstr): :param ipstr: The hyphenated IP range. """ - start, finish = ipstr.split('-') - ip = IPRange(start, finish) - log.debug('parse_range_style() start: %r' % start) - log.debug('parse_range_style() finish: %r' % finish) - log.debug('parse_range_style() ip: %r' % ip) + start, finish = ipstr.split("-") + ip_range = IPRange(start, finish) + log.debug("parse_range_style() start: %r", start) + log.debug("parse_range_style() finish: %r", finish) + log.debug("parse_range_style() ip: %r", ip_range) + + return ip_range - return ip def parse_commas(ipstr, **kwargs): """ @@ -158,8 +220,8 @@ def parse_commas(ipstr, **kwargs): A comma-separated string of IP address patterns. """ # Clean whitespace before we process - ipstr = ipstr.replace(' ', '').strip() - items = ipstr.split(',') + ipstr = ipstr.replace(" ", "").strip() + items = ipstr.split(",") # Possibly nested depending on input, so we'll run it thru itertools.chain # to flatten it. Then we make it a IPSet to optimize adjacencies and finally @@ -170,6 +232,7 @@ def parse_commas(ipstr, **kwargs): return ipset.iter_cidrs() + def is_ipv6(ipstr): """ Checks whether a string is IPv6 or not. Doesn't handle addresses with @@ -181,13 +244,14 @@ def is_ipv6(ipstr): :param ipstr: A suspected IPv6 address """ - log.debug('is_ipv6() got: %r' % ipstr) + log.debug("is_ipv6() got: %r" % ipstr) try: socket.inet_pton(socket.AF_INET6, ipstr) return True except (socket.error, AttributeError): return False + def cidrize(ipstr, strict=False, modular=True): """ This function tries to determine the best way to parse IP addresses correctly & has @@ -257,12 +321,12 @@ def cidrize(ipstr, strict=False, modular=True): ip = None # Short-circuit to parse commas since it calls back here anyway - if ',' in ipstr: + if "," in ipstr: return parse_commas(ipstr, strict=strict, modular=modular) # Short-circuit for hostnames (we're assuming first char is alpha) - if hostname_re.match(ipstr): - raise CidrizeError('Cannot parse hostnames!') + if RE_HOSTNAME.match(ipstr): + raise CidrizeError("Cannot parse hostnames!") # Otherwise try everything else result = None @@ -270,49 +334,51 @@ def cidrize(ipstr, strict=False, modular=True): # Parse "everything" & immediately return; strict/loose doesn't apply if ipstr in EVERYTHING: log.debug("Trying everything style...") - return [IPNetwork('0.0.0.0/0')] + return [IPNetwork("0.0.0.0/0")] # Parse old-fashioned CIDR notation & immediately return; strict/loose doesn't apply # Now with IPv6! - elif cidr_re.match(ipstr) or is_ipv6(ipstr): + elif RE_CIDR.match(ipstr) or is_ipv6(ipstr): log.debug("Trying CIDR style...") ip = IPNetwork(ipstr) return [ip.cidr] # Parse 1.2.3.118-1.2.3.121 range style - elif range_re.match(ipstr): + elif RE_RANGE.match(ipstr): log.debug("Trying range style...") result = parse_range(ipstr) # Parse 2001::14-2002::1.2.3.121 range style - elif range6_re.match(ipstr): + elif RE_RANGE6.match(ipstr): log.debug("Trying range6 style...") result = parse_range(ipstr) # Parse 1.2.3.4-70 hyphen style - elif hyphen_re.match(ipstr): + elif RE_HYPHEN.match(ipstr): log.debug("Trying hyphen style...") result = parse_hyphen(ipstr) # Parse 1.2.3.* glob style - elif glob_re.match(ipstr): + elif RE_GLOB.match(ipstr): log.debug("Trying glob style...") ipglob = IPGlob(ipstr) result = spanning_cidr(ipglob) # Parse 1.2.3.4[5-9] or 1.2.3.[49] bracket style as a last resort - elif bracket_re.match(ipstr): + elif RE_BRACKET.match(ipstr): log.debug("Trying bracket style...") result = parse_brackets(ipstr) # If result still isn't set, let's see if it's IPv6?? elif result is None: log.debug("Trying bare IPv6 parse...") - result = IPNetwork(ipstr) + result = IPNetwork(ipstr) # This will probably fail 100% of the time. By design. else: - raise CidrizeError("Could not determine parse style for '%s'" % ipstr) + raise CidrizeError( + "Could not determine parse style for '%s'" % ipstr + ) # If it's a single host, just return it wrapped in a list if result.size == 1: @@ -324,22 +390,25 @@ def cidrize(ipstr, strict=False, modular=True): # IPRange objects larger than MAX_RANGE_LEN will always be strict. if not strict: if isinstance(result, IPRange) and result.size >= MAX_RANGE_LEN: - log.debug('IPRange objects larger than /18 will always be strict.') + log.debug( + "IPRange objects larger than /18 will always be strict." + ) return result.cidrs() if isinstance(result, IPNetwork): return [result.cidr] return [spanning_cidr(result)] else: try: - return result.cidrs() # IPGlob and IPRange have .cidrs() + return result.cidrs() # IPGlob and IPRange have .cidrs() except AttributeError as err: - return [result.cidr] # IPNetwork has .cidr + return [result.cidr] # IPNetwork has .cidr except (AddrFormatError, TypeError, ValueError) as err: if modular: raise CidrizeError(err) return [str(err)] + def optimize_network_range(ipstr, threshold=0.9, verbose=DEBUG): """ Parses the input string and then calculates the subnet usage percentage. If over @@ -370,7 +439,7 @@ def optimize_network_range(ipstr, threshold=0.9, verbose=DEBUG): """ if threshold > 1 or threshold < 0: - raise CidrizeError('Threshold must be from 0.0 to 1.0') + raise CidrizeError("Threshold must be from 0.0 to 1.0") # Can't optimize 0.0.0.0/0! if ipstr in EVERYTHING: @@ -380,21 +449,21 @@ def optimize_network_range(ipstr, threshold=0.9, verbose=DEBUG): strict = IPSet(cidrize(ipstr, strict=True)) ratio = float(len(strict)) / float(len(loose)) - if verbose: - print('Subnet usage ratio: %s; Threshold: %s' % (ratio, threshold)) + verbose and print( + "Subnet usage ratio: %s; Threshold: %s" % (ratio, threshold) + ) if ratio >= threshold: - if verbose: - print('Over threshold, IP Parse Mode: LOOSE') + verbose and print("Over threshold, IP Parse Mode: LOOSE") result = loose.iter_cidrs() else: - if verbose: - print('Under threshold, IP Parse Mode: STRICT') + verbose and print("Under threshold, IP Parse Mode: STRICT") result = strict.iter_cidrs() return result -def output_str(ipobj, sep=', '): + +def output_str(ipobj, sep=", "): """ Returns a character-separated string of constituent CIDR blocks for a given IP object (should support both IPy and netaddr objects). @@ -407,6 +476,7 @@ def output_str(ipobj, sep=', '): """ return sep.join([str(x) for x in ipobj]) + def normalize_address(ipstr): """ Attempts to cleanup an IP address that is in a non-standard format such @@ -416,8 +486,8 @@ def normalize_address(ipstr): :param ipstr: An IP address string. """ - data = ipstr.split('/') - cidr = '32' + data = ipstr.split("/") + cidr = "32" if len(data) == 1: myip = data[0] elif len(data) == 2: @@ -425,9 +495,10 @@ def normalize_address(ipstr): else: return ipstr - octets = (int(i) for i in myip.split('.')) - ip = '.'.join([str(o) for o in octets]) - return '{0}/{1}'.format(ip, cidr) + octets = (int(i) for i in myip.split(".")) + ip = ".".join([str(o) for o in octets]) + return "{0}/{1}".format(ip, cidr) + def netaddr_to_ipy(iplist): """ @@ -448,6 +519,7 @@ def netaddr_to_ipy(iplist): return [IPy.IP(str(x)) for x in iplist] + def dump(cidr): """ Dumps a lot of info about a CIDR. @@ -462,10 +534,10 @@ def dump(cidr): cidr = spanning_cidr(cidr) # Is this a /32 or /128? - single = (cidr.size == 1) + single = cidr.size == 1 - log.debug('dump(): Single? %r' % single) - log.debug('dump(): Got? %r' % cidr) + log.debug("dump(): Single? %r", single) + log.debug("dump(): Got? %r", cidr) ip_first = IPAddress(cidr.first) ip_firsthost = ip_first if single else next(cidr.iter_hosts()) @@ -475,9 +547,9 @@ def dump(cidr): ip_hostmask = cidr.hostmask num_hosts = 1 if single else (cidr.last - 1) - cidr.first - out = '' + out = "" out += "Information for %s\n\n" % cidr - out += "IP Version:\t\t%s\n" % cidr.version + out += "IP Version:\t\t%s\n" % cidr.version out += "Spanning CIDR:\t\t%s\n" % cidr out += "Block Start/Network:\t%s\n" % ip_first out += "1st host:\t\t%s\n" % ip_firsthost @@ -490,16 +562,26 @@ def dump(cidr): return out + def parse_args(argv): """Parses args.""" from optparse import OptionParser - parser = OptionParser(usage='%prog [-v] [-d] [ip network]', add_help_option=0, description='''\ + + parser = OptionParser( + usage="%prog [-v] [-d] [ip network]", + add_help_option=0, + description="""\ Cidrize parses IP address notation and returns valid CIDR blocks. If you want -debug output set the DEBUG environment variable.''') +debug output set the DEBUG environment variable.""", + ) - parser.add_option('-h', '--help', action="store_false") - parser.add_option('-v', '--verbose', action='store_true', - help='Be verbose with user-friendly output. Lots of detail.') + parser.add_option("-h", "--help", action="store_false") + parser.add_option( + "-v", + "--verbose", + action="store_true", + help="Be verbose with user-friendly output. Lots of detail.", + ) notes = """ Intelligently take IPv4 addresses, CIDRs, ranges, and wildcard matches to attempt @@ -537,24 +619,27 @@ def phelp(): if opts.help or len(args) == 1: phelp() - sys.exit('ERROR: You must specify an ip address. See usage information above!!') + sys.exit( + "ERROR: You must specify an ip address. See usage information above!!" + ) else: opts.ip = args[1] - if ',' in opts.ip: + if "," in opts.ip: phelp() sys.exit("ERROR: Comma-separated arguments aren't supported!") return opts, args + def main(): """ Used by the 'cidr' command that is bundled with the package. """ opts, args = parse_args(sys.argv) - log.debug('OPTS: %r' % opts) - log.debug('ARGS: %r' % args) + log.debug("OPTS: %r", opts) + log.debug("ARGS: %r", args) ipstr = opts.ip @@ -562,11 +647,12 @@ def main(): cidr = cidrize(ipstr, modular=False) if cidr: if opts.verbose: - print(dump(cidr),) + print(dump(cidr)) else: print(output_str(cidr)) except IndexError: return -1 -if __name__ == '__main__': + +if __name__ == "__main__": sys.exit(main()) diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..2a3488a --- /dev/null +++ b/poetry.lock @@ -0,0 +1,738 @@ +[[package]] +name = "appdirs" +version = "1.4.4" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "appnope" +version = "0.1.2" +description = "Disable App Nap on macOS >= 10.9" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "astroid" +version = "2.5.2" +description = "An abstract syntax tree for Python with inference support." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +lazy-object-proxy = ">=1.4.0" +typed-ast = {version = ">=1.4.0,<1.5", markers = "implementation_name == \"cpython\" and python_version < \"3.8\""} +wrapt = ">=1.11,<1.13" + +[[package]] +name = "atomicwrites" +version = "1.4.0" +description = "Atomic file writes." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "attrs" +version = "20.3.0" +description = "Classes Without Boilerplate" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.extras] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"] +docs = ["furo", "sphinx", "zope.interface"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] + +[[package]] +name = "backcall" +version = "0.2.0" +description = "Specifications for callback functions passed in to an API" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "black" +version = "20.8b1" +description = "The uncompromising code formatter." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +appdirs = "*" +click = ">=7.1.2" +mypy-extensions = ">=0.4.3" +pathspec = ">=0.6,<1" +regex = ">=2020.1.8" +toml = ">=0.10.1" +typed-ast = ">=1.4.0" +typing-extensions = ">=3.7.4" + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] + +[[package]] +name = "click" +version = "7.1.2" +description = "Composable command line interface toolkit" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "colorama" +version = "0.4.4" +description = "Cross-platform colored terminal text." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "decorator" +version = "4.4.2" +description = "Decorators for Humans" +category = "dev" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*" + +[[package]] +name = "importlib-metadata" +version = "3.10.0" +description = "Read metadata from Python packages" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} +zipp = ">=0.5" + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] + +[[package]] +name = "iniconfig" +version = "1.1.1" +description = "iniconfig: brain-dead simple config-ini parsing" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "ipython" +version = "7.22.0" +description = "IPython: Productive Interactive Computing" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +appnope = {version = "*", markers = "sys_platform == \"darwin\""} +backcall = "*" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +decorator = "*" +jedi = ">=0.16" +pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} +pickleshare = "*" +prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0" +pygments = "*" +traitlets = ">=4.2" + +[package.extras] +all = ["Sphinx (>=1.3)", "ipykernel", "ipyparallel", "ipywidgets", "nbconvert", "nbformat", "nose (>=0.10.1)", "notebook", "numpy (>=1.16)", "pygments", "qtconsole", "requests", "testpath"] +doc = ["Sphinx (>=1.3)"] +kernel = ["ipykernel"] +nbconvert = ["nbconvert"] +nbformat = ["nbformat"] +notebook = ["notebook", "ipywidgets"] +parallel = ["ipyparallel"] +qtconsole = ["qtconsole"] +test = ["nose (>=0.10.1)", "requests", "testpath", "pygments", "nbformat", "ipykernel", "numpy (>=1.16)"] + +[[package]] +name = "ipython-genutils" +version = "0.2.0" +description = "Vestigial utilities from IPython" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "isort" +version = "5.8.0" +description = "A Python utility / library to sort Python imports." +category = "dev" +optional = false +python-versions = ">=3.6,<4.0" + +[package.extras] +pipfile_deprecated_finder = ["pipreqs", "requirementslib"] +requirements_deprecated_finder = ["pipreqs", "pip-api"] +colors = ["colorama (>=0.4.3,<0.5.0)"] + +[[package]] +name = "jedi" +version = "0.18.0" +description = "An autocompletion tool for Python that can be used for text editors." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +parso = ">=0.8.0,<0.9.0" + +[package.extras] +qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] +testing = ["Django (<3.1)", "colorama", "docopt", "pytest (<6.0.0)"] + +[[package]] +name = "lazy-object-proxy" +version = "1.6.0" +description = "A fast and thorough lazy object proxy." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" + +[[package]] +name = "mccabe" +version = "0.6.1" +description = "McCabe checker, plugin for flake8" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "mypy-extensions" +version = "0.4.3" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "netaddr" +version = "0.8.0" +description = "A network address manipulation library for Python" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "packaging" +version = "20.9" +description = "Core utilities for Python packages" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +pyparsing = ">=2.0.2" + +[[package]] +name = "parso" +version = "0.8.2" +description = "A Python Parser" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] +testing = ["docopt", "pytest (<6.0.0)"] + +[[package]] +name = "pathspec" +version = "0.8.1" +description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "pexpect" +version = "4.8.0" +description = "Pexpect allows easy control of interactive console applications." +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +ptyprocess = ">=0.5" + +[[package]] +name = "pickleshare" +version = "0.7.5" +description = "Tiny 'shelve'-like database with concurrency support" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "pluggy" +version = "0.13.1" +description = "plugin and hook calling mechanisms for python" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} + +[package.extras] +dev = ["pre-commit", "tox"] + +[[package]] +name = "prompt-toolkit" +version = "3.0.18" +description = "Library for building powerful interactive command lines in Python" +category = "dev" +optional = false +python-versions = ">=3.6.1" + +[package.dependencies] +wcwidth = "*" + +[[package]] +name = "ptyprocess" +version = "0.7.0" +description = "Run a subprocess in a pseudo terminal" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "py" +version = "1.10.0" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pygments" +version = "2.8.1" +description = "Pygments is a syntax highlighting package written in Python." +category = "dev" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "pylint" +version = "2.7.4" +description = "python code static checker" +category = "dev" +optional = false +python-versions = "~=3.6" + +[package.dependencies] +astroid = ">=2.5.2,<2.7" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +isort = ">=4.2.5,<6" +mccabe = ">=0.6,<0.7" +toml = ">=0.7.1" + +[package.extras] +docs = ["sphinx (==3.5.1)", "python-docs-theme (==2020.12)"] + +[[package]] +name = "pyparsing" +version = "2.4.7" +description = "Python parsing module" +category = "dev" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "pytest" +version = "6.2.2" +description = "pytest: simple powerful testing with Python" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} +attrs = ">=19.2.0" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<1.0.0a1" +py = ">=1.8.2" +toml = "*" + +[package.extras] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] + +[[package]] +name = "pytest-black" +version = "0.3.12" +description = "A pytest plugin to enable format checking with black" +category = "dev" +optional = false +python-versions = ">=2.7" + +[package.dependencies] +black = {version = "*", markers = "python_version >= \"3.6\""} +pytest = ">=3.5.0" +toml = "*" + +[[package]] +name = "pytest-pylint" +version = "0.18.0" +description = "pytest plugin to check source code with pylint" +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +pylint = ">=2.3.0" +pytest = ">=5.4" +toml = ">=0.7.1" + +[[package]] +name = "regex" +version = "2021.3.17" +description = "Alternative regular expression module, to replace re." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +category = "dev" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "traitlets" +version = "5.0.5" +description = "Traitlets Python configuration system" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +ipython-genutils = "*" + +[package.extras] +test = ["pytest"] + +[[package]] +name = "typed-ast" +version = "1.4.2" +description = "a fork of Python 2 and 3 ast modules with type comment support" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "typing-extensions" +version = "3.7.4.3" +description = "Backported and Experimental Type Hints for Python 3.5+" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "wcwidth" +version = "0.2.5" +description = "Measures the displayed width of unicode strings in a terminal" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "wrapt" +version = "1.12.1" +description = "Module for decorators, wrappers and monkey patching." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "zipp" +version = "3.4.1" +description = "Backport of pathlib-compatible object wrapper for zip files" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] + +[metadata] +lock-version = "1.1" +python-versions = "^3.7" +content-hash = "446c12008a3794aaae55db5578b6a35847189fcbbfafed82e688117da754a93e" + +[metadata.files] +appdirs = [ + {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, + {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, +] +appnope = [ + {file = "appnope-0.1.2-py2.py3-none-any.whl", hash = "sha256:93aa393e9d6c54c5cd570ccadd8edad61ea0c4b9ea7a01409020c9aa019eb442"}, + {file = "appnope-0.1.2.tar.gz", hash = "sha256:dd83cd4b5b460958838f6eb3000c660b1f9caf2a5b1de4264e941512f603258a"}, +] +astroid = [ + {file = "astroid-2.5.2-py3-none-any.whl", hash = "sha256:cd80bf957c49765dce6d92c43163ff9d2abc43132ce64d4b1b47717c6d2522df"}, + {file = "astroid-2.5.2.tar.gz", hash = "sha256:6b0ed1af831570e500e2437625979eaa3b36011f66ddfc4ce930128610258ca9"}, +] +atomicwrites = [ + {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, + {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, +] +attrs = [ + {file = "attrs-20.3.0-py2.py3-none-any.whl", hash = "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6"}, + {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"}, +] +backcall = [ + {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, + {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, +] +black = [ + {file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"}, +] +click = [ + {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, + {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, +] +colorama = [ + {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, + {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, +] +decorator = [ + {file = "decorator-4.4.2-py2.py3-none-any.whl", hash = "sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760"}, + {file = "decorator-4.4.2.tar.gz", hash = "sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7"}, +] +importlib-metadata = [ + {file = "importlib_metadata-3.10.0-py3-none-any.whl", hash = "sha256:d2d46ef77ffc85cbf7dac7e81dd663fde71c45326131bea8033b9bad42268ebe"}, + {file = "importlib_metadata-3.10.0.tar.gz", hash = "sha256:c9db46394197244adf2f0b08ec5bc3cf16757e9590b02af1fca085c16c0d600a"}, +] +iniconfig = [ + {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, + {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, +] +ipython = [ + {file = "ipython-7.22.0-py3-none-any.whl", hash = "sha256:c0ce02dfaa5f854809ab7413c601c4543846d9da81010258ecdab299b542d199"}, + {file = "ipython-7.22.0.tar.gz", hash = "sha256:9c900332d4c5a6de534b4befeeb7de44ad0cc42e8327fa41b7685abde58cec74"}, +] +ipython-genutils = [ + {file = "ipython_genutils-0.2.0-py2.py3-none-any.whl", hash = "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8"}, + {file = "ipython_genutils-0.2.0.tar.gz", hash = "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"}, +] +isort = [ + {file = "isort-5.8.0-py3-none-any.whl", hash = "sha256:2bb1680aad211e3c9944dbce1d4ba09a989f04e238296c87fe2139faa26d655d"}, + {file = "isort-5.8.0.tar.gz", hash = "sha256:0a943902919f65c5684ac4e0154b1ad4fac6dcaa5d9f3426b732f1c8b5419be6"}, +] +jedi = [ + {file = "jedi-0.18.0-py2.py3-none-any.whl", hash = "sha256:18456d83f65f400ab0c2d3319e48520420ef43b23a086fdc05dff34132f0fb93"}, + {file = "jedi-0.18.0.tar.gz", hash = "sha256:92550a404bad8afed881a137ec9a461fed49eca661414be45059329614ed0707"}, +] +lazy-object-proxy = [ + {file = "lazy-object-proxy-1.6.0.tar.gz", hash = "sha256:489000d368377571c6f982fba6497f2aa13c6d1facc40660963da62f5c379726"}, + {file = "lazy_object_proxy-1.6.0-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:c6938967f8528b3668622a9ed3b31d145fab161a32f5891ea7b84f6b790be05b"}, + {file = "lazy_object_proxy-1.6.0-cp27-cp27m-win32.whl", hash = "sha256:ebfd274dcd5133e0afae738e6d9da4323c3eb021b3e13052d8cbd0e457b1256e"}, + {file = "lazy_object_proxy-1.6.0-cp27-cp27m-win_amd64.whl", hash = "sha256:ed361bb83436f117f9917d282a456f9e5009ea12fd6de8742d1a4752c3017e93"}, + {file = "lazy_object_proxy-1.6.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d900d949b707778696fdf01036f58c9876a0d8bfe116e8d220cfd4b15f14e741"}, + {file = "lazy_object_proxy-1.6.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:5743a5ab42ae40caa8421b320ebf3a998f89c85cdc8376d6b2e00bd12bd1b587"}, + {file = "lazy_object_proxy-1.6.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:bf34e368e8dd976423396555078def5cfc3039ebc6fc06d1ae2c5a65eebbcde4"}, + {file = "lazy_object_proxy-1.6.0-cp36-cp36m-win32.whl", hash = "sha256:b579f8acbf2bdd9ea200b1d5dea36abd93cabf56cf626ab9c744a432e15c815f"}, + {file = "lazy_object_proxy-1.6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:4f60460e9f1eb632584c9685bccea152f4ac2130e299784dbaf9fae9f49891b3"}, + {file = "lazy_object_proxy-1.6.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d7124f52f3bd259f510651450e18e0fd081ed82f3c08541dffc7b94b883aa981"}, + {file = "lazy_object_proxy-1.6.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:22ddd618cefe54305df49e4c069fa65715be4ad0e78e8d252a33debf00f6ede2"}, + {file = "lazy_object_proxy-1.6.0-cp37-cp37m-win32.whl", hash = "sha256:9d397bf41caad3f489e10774667310d73cb9c4258e9aed94b9ec734b34b495fd"}, + {file = "lazy_object_proxy-1.6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:24a5045889cc2729033b3e604d496c2b6f588c754f7a62027ad4437a7ecc4837"}, + {file = "lazy_object_proxy-1.6.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:17e0967ba374fc24141738c69736da90e94419338fd4c7c7bef01ee26b339653"}, + {file = "lazy_object_proxy-1.6.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:410283732af311b51b837894fa2f24f2c0039aa7f220135192b38fcc42bd43d3"}, + {file = "lazy_object_proxy-1.6.0-cp38-cp38-win32.whl", hash = "sha256:85fb7608121fd5621cc4377a8961d0b32ccf84a7285b4f1d21988b2eae2868e8"}, + {file = "lazy_object_proxy-1.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:d1c2676e3d840852a2de7c7d5d76407c772927addff8d742b9808fe0afccebdf"}, + {file = "lazy_object_proxy-1.6.0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:b865b01a2e7f96db0c5d12cfea590f98d8c5ba64ad222300d93ce6ff9138bcad"}, + {file = "lazy_object_proxy-1.6.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:4732c765372bd78a2d6b2150a6e99d00a78ec963375f236979c0626b97ed8e43"}, + {file = "lazy_object_proxy-1.6.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:9698110e36e2df951c7c36b6729e96429c9c32b3331989ef19976592c5f3c77a"}, + {file = "lazy_object_proxy-1.6.0-cp39-cp39-win32.whl", hash = "sha256:1fee665d2638491f4d6e55bd483e15ef21f6c8c2095f235fef72601021e64f61"}, + {file = "lazy_object_proxy-1.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:f5144c75445ae3ca2057faac03fda5a902eff196702b0a24daf1d6ce0650514b"}, +] +mccabe = [ + {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, + {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, +] +mypy-extensions = [ + {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, + {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, +] +netaddr = [ + {file = "netaddr-0.8.0-py2.py3-none-any.whl", hash = "sha256:9666d0232c32d2656e5e5f8d735f58fd6c7457ce52fc21c98d45f2af78f990ac"}, + {file = "netaddr-0.8.0.tar.gz", hash = "sha256:d6cc57c7a07b1d9d2e917aa8b36ae8ce61c35ba3fcd1b83ca31c5a0ee2b5a243"}, +] +packaging = [ + {file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"}, + {file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"}, +] +parso = [ + {file = "parso-0.8.2-py2.py3-none-any.whl", hash = "sha256:a8c4922db71e4fdb90e0d0bc6e50f9b273d3397925e5e60a717e719201778d22"}, + {file = "parso-0.8.2.tar.gz", hash = "sha256:12b83492c6239ce32ff5eed6d3639d6a536170723c6f3f1506869f1ace413398"}, +] +pathspec = [ + {file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"}, + {file = "pathspec-0.8.1.tar.gz", hash = "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd"}, +] +pexpect = [ + {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"}, + {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"}, +] +pickleshare = [ + {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"}, + {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, +] +pluggy = [ + {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, + {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, +] +prompt-toolkit = [ + {file = "prompt_toolkit-3.0.18-py3-none-any.whl", hash = "sha256:bf00f22079f5fadc949f42ae8ff7f05702826a97059ffcc6281036ad40ac6f04"}, + {file = "prompt_toolkit-3.0.18.tar.gz", hash = "sha256:e1b4f11b9336a28fa11810bc623c357420f69dfdb6d2dac41ca2c21a55c033bc"}, +] +ptyprocess = [ + {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, + {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, +] +py = [ + {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, + {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, +] +pygments = [ + {file = "Pygments-2.8.1-py3-none-any.whl", hash = "sha256:534ef71d539ae97d4c3a4cf7d6f110f214b0e687e92f9cb9d2a3b0d3101289c8"}, + {file = "Pygments-2.8.1.tar.gz", hash = "sha256:2656e1a6edcdabf4275f9a3640db59fd5de107d88e8663c5d4e9a0fa62f77f94"}, +] +pylint = [ + {file = "pylint-2.7.4-py3-none-any.whl", hash = "sha256:209d712ec870a0182df034ae19f347e725c1e615b2269519ab58a35b3fcbbe7a"}, + {file = "pylint-2.7.4.tar.gz", hash = "sha256:bd38914c7731cdc518634a8d3c5585951302b6e2b6de60fbb3f7a0220e21eeee"}, +] +pyparsing = [ + {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, + {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, +] +pytest = [ + {file = "pytest-6.2.2-py3-none-any.whl", hash = "sha256:b574b57423e818210672e07ca1fa90aaf194a4f63f3ab909a2c67ebb22913839"}, + {file = "pytest-6.2.2.tar.gz", hash = "sha256:9d1edf9e7d0b84d72ea3dbcdfd22b35fb543a5e8f2a60092dd578936bf63d7f9"}, +] +pytest-black = [ + {file = "pytest-black-0.3.12.tar.gz", hash = "sha256:1d339b004f764d6cd0f06e690f6dd748df3d62e6fe1a692d6a5500ac2c5b75a5"}, +] +pytest-pylint = [ + {file = "pytest-pylint-0.18.0.tar.gz", hash = "sha256:790c7a8019fab08e59bd3812db1657a01995a975af8b1c6ce95b9aa39d61da27"}, + {file = "pytest_pylint-0.18.0-py3-none-any.whl", hash = "sha256:b63aaf8b80ff33c8ceaa7f68323ed04102c7790093ccf6bdb261a4c2dc6fd564"}, +] +regex = [ + {file = "regex-2021.3.17-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b97ec5d299c10d96617cc851b2e0f81ba5d9d6248413cd374ef7f3a8871ee4a6"}, + {file = "regex-2021.3.17-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:cb4ee827857a5ad9b8ae34d3c8cc51151cb4a3fe082c12ec20ec73e63cc7c6f0"}, + {file = "regex-2021.3.17-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:633497504e2a485a70a3268d4fc403fe3063a50a50eed1039083e9471ad0101c"}, + {file = "regex-2021.3.17-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:a59a2ee329b3de764b21495d78c92ab00b4ea79acef0f7ae8c1067f773570afa"}, + {file = "regex-2021.3.17-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:f85d6f41e34f6a2d1607e312820971872944f1661a73d33e1e82d35ea3305e14"}, + {file = "regex-2021.3.17-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:4651f839dbde0816798e698626af6a2469eee6d9964824bb5386091255a1694f"}, + {file = "regex-2021.3.17-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:39c44532d0e4f1639a89e52355b949573e1e2c5116106a395642cbbae0ff9bcd"}, + {file = "regex-2021.3.17-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:3d9a7e215e02bd7646a91fb8bcba30bc55fd42a719d6b35cf80e5bae31d9134e"}, + {file = "regex-2021.3.17-cp36-cp36m-win32.whl", hash = "sha256:159fac1a4731409c830d32913f13f68346d6b8e39650ed5d704a9ce2f9ef9cb3"}, + {file = "regex-2021.3.17-cp36-cp36m-win_amd64.whl", hash = "sha256:13f50969028e81765ed2a1c5fcfdc246c245cf8d47986d5172e82ab1a0c42ee5"}, + {file = "regex-2021.3.17-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b9d8d286c53fe0cbc6d20bf3d583cabcd1499d89034524e3b94c93a5ab85ca90"}, + {file = "regex-2021.3.17-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:201e2619a77b21a7780580ab7b5ce43835e242d3e20fef50f66a8df0542e437f"}, + {file = "regex-2021.3.17-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d47d359545b0ccad29d572ecd52c9da945de7cd6cf9c0cfcb0269f76d3555689"}, + {file = "regex-2021.3.17-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:ea2f41445852c660ba7c3ebf7d70b3779b20d9ca8ba54485a17740db49f46932"}, + {file = "regex-2021.3.17-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:486a5f8e11e1f5bbfcad87f7c7745eb14796642323e7e1829a331f87a713daaa"}, + {file = "regex-2021.3.17-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:18e25e0afe1cf0f62781a150c1454b2113785401ba285c745acf10c8ca8917df"}, + {file = "regex-2021.3.17-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:a2ee026f4156789df8644d23ef423e6194fad0bc53575534101bb1de5d67e8ce"}, + {file = "regex-2021.3.17-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:4c0788010a93ace8a174d73e7c6c9d3e6e3b7ad99a453c8ee8c975ddd9965643"}, + {file = "regex-2021.3.17-cp37-cp37m-win32.whl", hash = "sha256:575a832e09d237ae5fedb825a7a5bc6a116090dd57d6417d4f3b75121c73e3be"}, + {file = "regex-2021.3.17-cp37-cp37m-win_amd64.whl", hash = "sha256:8e65e3e4c6feadf6770e2ad89ad3deb524bcb03d8dc679f381d0568c024e0deb"}, + {file = "regex-2021.3.17-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a0df9a0ad2aad49ea3c7f65edd2ffb3d5c59589b85992a6006354f6fb109bb18"}, + {file = "regex-2021.3.17-cp38-cp38-manylinux1_i686.whl", hash = "sha256:b98bc9db003f1079caf07b610377ed1ac2e2c11acc2bea4892e28cc5b509d8d5"}, + {file = "regex-2021.3.17-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:808404898e9a765e4058bf3d7607d0629000e0a14a6782ccbb089296b76fa8fe"}, + {file = "regex-2021.3.17-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:5770a51180d85ea468234bc7987f5597803a4c3d7463e7323322fe4a1b181578"}, + {file = "regex-2021.3.17-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:976a54d44fd043d958a69b18705a910a8376196c6b6ee5f2596ffc11bff4420d"}, + {file = "regex-2021.3.17-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:63f3ca8451e5ff7133ffbec9eda641aeab2001be1a01878990f6c87e3c44b9d5"}, + {file = "regex-2021.3.17-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:bcd945175c29a672f13fce13a11893556cd440e37c1b643d6eeab1988c8b209c"}, + {file = "regex-2021.3.17-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:3d9356add82cff75413bec360c1eca3e58db4a9f5dafa1f19650958a81e3249d"}, + {file = "regex-2021.3.17-cp38-cp38-win32.whl", hash = "sha256:f5d0c921c99297354cecc5a416ee4280bd3f20fd81b9fb671ca6be71499c3fdf"}, + {file = "regex-2021.3.17-cp38-cp38-win_amd64.whl", hash = "sha256:14de88eda0976020528efc92d0a1f8830e2fb0de2ae6005a6fc4e062553031fa"}, + {file = "regex-2021.3.17-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4c2e364491406b7888c2ad4428245fc56c327e34a5dfe58fd40df272b3c3dab3"}, + {file = "regex-2021.3.17-cp39-cp39-manylinux1_i686.whl", hash = "sha256:8bd4f91f3fb1c9b1380d6894bd5b4a519409135bec14c0c80151e58394a4e88a"}, + {file = "regex-2021.3.17-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:882f53afe31ef0425b405a3f601c0009b44206ea7f55ee1c606aad3cc213a52c"}, + {file = "regex-2021.3.17-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:07ef35301b4484bce843831e7039a84e19d8d33b3f8b2f9aab86c376813d0139"}, + {file = "regex-2021.3.17-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:360a01b5fa2ad35b3113ae0c07fb544ad180603fa3b1f074f52d98c1096fa15e"}, + {file = "regex-2021.3.17-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:709f65bb2fa9825f09892617d01246002097f8f9b6dde8d1bb4083cf554701ba"}, + {file = "regex-2021.3.17-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:c66221e947d7207457f8b6f42b12f613b09efa9669f65a587a2a71f6a0e4d106"}, + {file = "regex-2021.3.17-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:c782da0e45aff131f0bed6e66fbcfa589ff2862fc719b83a88640daa01a5aff7"}, + {file = "regex-2021.3.17-cp39-cp39-win32.whl", hash = "sha256:dc9963aacb7da5177e40874585d7407c0f93fb9d7518ec58b86e562f633f36cd"}, + {file = "regex-2021.3.17-cp39-cp39-win_amd64.whl", hash = "sha256:a0d04128e005142260de3733591ddf476e4902c0c23c1af237d9acf3c96e1b38"}, + {file = "regex-2021.3.17.tar.gz", hash = "sha256:4b8a1fb724904139149a43e172850f35aa6ea97fb0545244dc0b805e0154ed68"}, +] +toml = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] +traitlets = [ + {file = "traitlets-5.0.5-py3-none-any.whl", hash = "sha256:69ff3f9d5351f31a7ad80443c2674b7099df13cc41fc5fa6e2f6d3b0330b0426"}, + {file = "traitlets-5.0.5.tar.gz", hash = "sha256:178f4ce988f69189f7e523337a3e11d91c786ded9360174a3d9ca83e79bc5396"}, +] +typed-ast = [ + {file = "typed_ast-1.4.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:7703620125e4fb79b64aa52427ec192822e9f45d37d4b6625ab37ef403e1df70"}, + {file = "typed_ast-1.4.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c9aadc4924d4b5799112837b226160428524a9a45f830e0d0f184b19e4090487"}, + {file = "typed_ast-1.4.2-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:9ec45db0c766f196ae629e509f059ff05fc3148f9ffd28f3cfe75d4afb485412"}, + {file = "typed_ast-1.4.2-cp35-cp35m-win32.whl", hash = "sha256:85f95aa97a35bdb2f2f7d10ec5bbdac0aeb9dafdaf88e17492da0504de2e6400"}, + {file = "typed_ast-1.4.2-cp35-cp35m-win_amd64.whl", hash = "sha256:9044ef2df88d7f33692ae3f18d3be63dec69c4fb1b5a4a9ac950f9b4ba571606"}, + {file = "typed_ast-1.4.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c1c876fd795b36126f773db9cbb393f19808edd2637e00fd6caba0e25f2c7b64"}, + {file = "typed_ast-1.4.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:5dcfc2e264bd8a1db8b11a892bd1647154ce03eeba94b461effe68790d8b8e07"}, + {file = "typed_ast-1.4.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:8db0e856712f79c45956da0c9a40ca4246abc3485ae0d7ecc86a20f5e4c09abc"}, + {file = "typed_ast-1.4.2-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:d003156bb6a59cda9050e983441b7fa2487f7800d76bdc065566b7d728b4581a"}, + {file = "typed_ast-1.4.2-cp36-cp36m-win32.whl", hash = "sha256:4c790331247081ea7c632a76d5b2a265e6d325ecd3179d06e9cf8d46d90dd151"}, + {file = "typed_ast-1.4.2-cp36-cp36m-win_amd64.whl", hash = "sha256:d175297e9533d8d37437abc14e8a83cbc68af93cc9c1c59c2c292ec59a0697a3"}, + {file = "typed_ast-1.4.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf54cfa843f297991b7388c281cb3855d911137223c6b6d2dd82a47ae5125a41"}, + {file = "typed_ast-1.4.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:b4fcdcfa302538f70929eb7b392f536a237cbe2ed9cba88e3bf5027b39f5f77f"}, + {file = "typed_ast-1.4.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:987f15737aba2ab5f3928c617ccf1ce412e2e321c77ab16ca5a293e7bbffd581"}, + {file = "typed_ast-1.4.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:37f48d46d733d57cc70fd5f30572d11ab8ed92da6e6b28e024e4a3edfb456e37"}, + {file = "typed_ast-1.4.2-cp37-cp37m-win32.whl", hash = "sha256:36d829b31ab67d6fcb30e185ec996e1f72b892255a745d3a82138c97d21ed1cd"}, + {file = "typed_ast-1.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:8368f83e93c7156ccd40e49a783a6a6850ca25b556c0fa0240ed0f659d2fe496"}, + {file = "typed_ast-1.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:963c80b583b0661918718b095e02303d8078950b26cc00b5e5ea9ababe0de1fc"}, + {file = "typed_ast-1.4.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e683e409e5c45d5c9082dc1daf13f6374300806240719f95dc783d1fc942af10"}, + {file = "typed_ast-1.4.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:84aa6223d71012c68d577c83f4e7db50d11d6b1399a9c779046d75e24bed74ea"}, + {file = "typed_ast-1.4.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:a38878a223bdd37c9709d07cd357bb79f4c760b29210e14ad0fb395294583787"}, + {file = "typed_ast-1.4.2-cp38-cp38-win32.whl", hash = "sha256:a2c927c49f2029291fbabd673d51a2180038f8cd5a5b2f290f78c4516be48be2"}, + {file = "typed_ast-1.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:c0c74e5579af4b977c8b932f40a5464764b2f86681327410aa028a22d2f54937"}, + {file = "typed_ast-1.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:07d49388d5bf7e863f7fa2f124b1b1d89d8aa0e2f7812faff0a5658c01c59aa1"}, + {file = "typed_ast-1.4.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:240296b27397e4e37874abb1df2a608a92df85cf3e2a04d0d4d61055c8305ba6"}, + {file = "typed_ast-1.4.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:d746a437cdbca200622385305aedd9aef68e8a645e385cc483bdc5e488f07166"}, + {file = "typed_ast-1.4.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:14bf1522cdee369e8f5581238edac09150c765ec1cb33615855889cf33dcb92d"}, + {file = "typed_ast-1.4.2-cp39-cp39-win32.whl", hash = "sha256:cc7b98bf58167b7f2db91a4327da24fb93368838eb84a44c472283778fc2446b"}, + {file = "typed_ast-1.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:7147e2a76c75f0f64c4319886e7639e490fee87c9d25cb1d4faef1d8cf83a440"}, + {file = "typed_ast-1.4.2.tar.gz", hash = "sha256:9fc0b3cb5d1720e7141d103cf4819aea239f7d136acf9ee4a69b047b7986175a"}, +] +typing-extensions = [ + {file = "typing_extensions-3.7.4.3-py2-none-any.whl", hash = "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"}, + {file = "typing_extensions-3.7.4.3-py3-none-any.whl", hash = "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918"}, + {file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"}, +] +wcwidth = [ + {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, + {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, +] +wrapt = [ + {file = "wrapt-1.12.1.tar.gz", hash = "sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7"}, +] +zipp = [ + {file = "zipp-3.4.1-py3-none-any.whl", hash = "sha256:51cb66cc54621609dd593d1787f286ee42a5c0adbb4b29abea5a63edc3e03098"}, + {file = "zipp-3.4.1.tar.gz", hash = "sha256:3607921face881ba3e026887d8150cca609d517579abe052ac81fc5aeffdbd76"}, +] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..14f43be --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,105 @@ +[tool.poetry] +name = "cidrize" +version = "2.0.0" +description = "Cidrize parses IPv4/IPv6 addresses, CIDRs, ranges, and wildcard matches & attempts to return a valid list of IP addresses" +authors = ["Jathan McCollum "] +license = "Apache-2.0" +readme = "README.rst" +keywords = [ + "Networking", + "Systems", + "Administration", + "IANA", + "IEEE", + "CIDR", + "IP", + "IPv4", + "IPv6", + "IP", + "Address", + "Firewalls", + "Security" +] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Environment :: Console", + "Environment :: Plugins", + "Intended Audience :: Developers", + "Intended Audience :: Education", + "Intended Audience :: Information Technology", + "Intended Audience :: Science/Research", + "Intended Audience :: System Administrators", + "Intended Audience :: Telecommunications Industry", + "Natural Language :: English", + "License :: OSI Approved :: BSD License", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3 :: Only", + "Topic :: Education :: Testing", + "Topic :: Internet", + "Topic :: Internet :: Name Service (DNS)", + "Topic :: Software Development", + "Topic :: Software Development :: Libraries :: Python Modules", + "Topic :: System :: Networking", + "Topic :: System :: Networking :: Firewalls", + "Topic :: System :: Networking :: Monitoring", + "Topic :: System :: Operating System", + "Topic :: System :: Systems Administration", + "Topic :: Utilities" +] +homepage = "https://github.com/jathanism/cidrize/" +packages = [ + {include = "cidrize.py"} +] +include = [ + "CHANGELOG.rst", + "TODO", + "examples/*", +] + +[tool.poetry.dependencies] +python = "^3.7" +netaddr = "~=0.8.0" + +[tool.poetry.dev-dependencies] +ipython = "^7.22.0" +pytest = "^6.2.2" +black = "^20.8b1" +pytest-black = "^0.3.12" +pylint = "^2.7.4" +pytest-pylint = "^0.18.0" + +[tool.poetry.scripts] +cidr = "cidrize:main" + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" + +[tool.black] +line-length = 80 +target-version = ['py37'] +include = '\.pyi?$' +exclude = ''' +( + /( + \.eggs # exclude a few common directories in the + | \.git # root of the project + | \.hg + | \.mypy_cache + | \.tox + | \.venv + | _build + | buck-out + | build + | dist + )/ +) +''' + +[tool.pytest.ini_options] +minversion = "6.0" +addopts = "-vv" +testpaths = [ + "tests", +] diff --git a/scripts/cidr b/scripts/cidr deleted file mode 100755 index e986ea3..0000000 --- a/scripts/cidr +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env python - -from cidrize import main -import sys - -if __name__ == '__main__': - sys.exit(main()) diff --git a/tests/test_cidrize.py b/tests/test_cidrize.py index fae8b84..2a1dee1 100755 --- a/tests/test_cidrize.py +++ b/tests/test_cidrize.py @@ -1,138 +1,155 @@ #!/usr/bin/env python import unittest -from netaddr import (IPRange, IPAddress, IPNetwork,) + +from netaddr import ( + IPRange, + IPAddress, + IPNetwork, +) +import pytest + import cidrize class TestParseBrackets(unittest.TestCase): def test_parse_brackets(self): - expected = IPRange('1.2.3.118', '1.2.3.121') - _input = '1.2.3.1[18-21]' - self.assertEqual(expected, cidrize.parse_brackets(_input)) + expected = IPRange("1.2.3.118", "1.2.3.121") + _input = "1.2.3.1[18-21]" + assert expected == cidrize.parse_brackets(_input) def test_parse_brackets_fail(self): expected = None - _input = '10.10.1[67].0/24' - self.assertEqual(expected, cidrize.parse_brackets(_input)) + _input = "10.10.1[67].0/24" + assert expected == cidrize.parse_brackets(_input) + class TestCidrize(unittest.TestCase): def setUp(self): self.test = cidrize.cidrize def test_everything_style(self): - expected = set([IPNetwork('0.0.0.0/0')]) + expected = set([IPNetwork("0.0.0.0/0")]) _input = set() [_input.add(self.test(item)[0]) for item in cidrize.EVERYTHING] - self.assertEqual(expected, _input) + assert expected == _input def test_cidr_style_ipv4(self): - expected = [IPNetwork('1.2.3.4/32')] - _input = '1.2.3.4' - self.assertEqual(expected, self.test(_input)) + expected = [IPNetwork("1.2.3.4/32")] + _input = "1.2.3.4" + assert expected == self.test(_input) def test_cidr_style_ipv6(self): - expected = [IPNetwork('fe80:4::c62c:3ff:fe00:861e/128')] - _input = 'fe80:4::c62c:3ff:fe00:861e' - self.assertEqual(expected, self.test(_input)) + expected = [IPNetwork("fe80:4::c62c:3ff:fe00:861e/128")] + _input = "fe80:4::c62c:3ff:fe00:861e" + assert expected == self.test(_input) def test_parse_range(self): - expected = IPRange('1.2.3.4', '1.2.3.10') - _input = '1.2.3.4-1.2.3.10' - self.assertEqual(expected, cidrize.parse_range(_input)) + expected = IPRange("1.2.3.4", "1.2.3.10") + _input = "1.2.3.4-1.2.3.10" + assert expected == cidrize.parse_range(_input) def test_parse_range6(self): - expected = IPRange('2001::1.2.3.4', '2002:1234:abcd::ffee') - _input = '2001::1.2.3.4-2002:1234:abcd::ffee' - self.assertEqual(expected, cidrize.parse_range(_input)) + expected = IPRange("2001::1.2.3.4", "2002:1234:abcd::ffee") + _input = "2001::1.2.3.4-2002:1234:abcd::ffee" + assert expected == cidrize.parse_range(_input) def test_range_style_strict(self): - expected = [IPNetwork('1.2.3.118/31'), IPNetwork('1.2.3.120/31')] - _input = '1.2.3.118-1.2.3.121' - self.assertEqual(expected, self.test(_input, strict=True)) + expected = [IPNetwork("1.2.3.118/31"), IPNetwork("1.2.3.120/31")] + _input = "1.2.3.118-1.2.3.121" + assert expected == self.test(_input, strict=True) def test_range_style_loose(self): - expected = [IPNetwork('1.2.3.112/28')] - _input = '1.2.3.118-1.2.3.121' - self.assertEqual(expected, self.test(_input, strict=False)) + expected = [IPNetwork("1.2.3.112/28")] + _input = "1.2.3.118-1.2.3.121" + assert expected == self.test(_input, strict=False) def test_glob_style(self): - expected = [IPNetwork('1.2.3.0/24')] - _input = '1.2.3.*' - self.assertEqual(expected, self.test(_input)) + expected = [IPNetwork("1.2.3.0/24")] + _input = "1.2.3.*" + assert expected == self.test(_input) def test_hyphen_style_strict(self): - expected = [IPNetwork('1.2.3.4/30'), IPNetwork('1.2.3.8/29'), - IPNetwork('1.2.3.16/30'), IPNetwork('1.2.3.20/32')] - _input = '1.2.3.4-20' - self.assertEqual(expected, self.test(_input, strict=True)) + expected = [ + IPNetwork("1.2.3.4/30"), + IPNetwork("1.2.3.8/29"), + IPNetwork("1.2.3.16/30"), + IPNetwork("1.2.3.20/32"), + ] + _input = "1.2.3.4-20" + assert expected == self.test(_input, strict=True) def test_hyphen_style_loose(self): - expected = [IPNetwork('1.2.3.0/27')] - _input = '1.2.3.4-20' - self.assertEqual(expected, self.test(_input, strict=False)) + expected = [IPNetwork("1.2.3.0/27")] + _input = "1.2.3.4-20" + assert expected == self.test(_input, strict=False) def test_hyphen_style_loose_toobig(self): # IPRange objects larger than /18 will always be strict. - expected = [IPNetwork('10.0.0.0/18'), IPNetwork('10.0.64.0/29')] - _input = '10.0.0.0-10.0.64.7' - self.assertEqual(expected, self.test(_input, strict=False)) + expected = [IPNetwork("10.0.0.0/18"), IPNetwork("10.0.64.0/29")] + _input = "10.0.0.0-10.0.64.7" + assert expected == self.test(_input, strict=False) def test_bracket_style_strict(self): - expected = [IPNetwork('1.2.3.118/31'), IPNetwork('1.2.3.120/31')] - _input = '1.2.3.1[18-21]' - self.assertEqual(expected, self.test(_input, strict=True)) + expected = [IPNetwork("1.2.3.118/31"), IPNetwork("1.2.3.120/31")] + _input = "1.2.3.1[18-21]" + assert expected == self.test(_input, strict=True) def test_bracket_style_loose(self): - expected = [IPNetwork('1.2.3.112/28')] - _input = '1.2.3.1[18-21]' - self.assertEqual(expected, self.test(_input, strict=False)) + expected = [IPNetwork("1.2.3.112/28")] + _input = "1.2.3.1[18-21]" + assert expected == self.test(_input, strict=False) def test_hostname(self): - _input = 'jathan.com' - self.assertRaises(cidrize.CidrizeError, self.test, _input) + _input = "jathan.com" + with pytest.raises(cidrize.CidrizeError): + self.test(_input) def test_nocidr_ipv6(self): - expected = [IPNetwork('2001:4b0:1668:2602::2/128')] - _input = '2001:4b0:1668:2602::2' - self.assertEqual(expected, self.test(_input)) + expected = [IPNetwork("2001:4b0:1668:2602::2/128")] + _input = "2001:4b0:1668:2602::2" + assert expected == self.test(_input) def test_last_resort_ipv6(self): - expected = [IPNetwork('2001:4b0:1668:2602::2/128')] - _input = '2001:4b0:1668:2602::2/128' - self.assertEqual(expected, self.test(_input)) + expected = [IPNetwork("2001:4b0:1668:2602::2/128")] + _input = "2001:4b0:1668:2602::2/128" + assert expected == self.test(_input) def test_large_ipv6(self): - expected = [IPNetwork('2001:4b0:1668:2602::2/64')] - _input = '2001:4b0:1668:2602::/64' - self.assertEqual(expected, self.test(_input)) + expected = [IPNetwork("2001:4b0:1668:2602::2/64")] + _input = "2001:4b0:1668:2602::/64" + assert expected == self.test(_input) def test_failure(self): - _input = '1.2.3.4]' - self.assertRaises(cidrize.CidrizeError, self.test, _input) + _input = "1.2.3.4]" + with pytest.raises(cidrize.CidrizeError): + self.test(_input) def test_bracket_failure(self): - _input = '10.10.1[67].0/24' - self.assertRaises(cidrize.CidrizeError, self.test, _input) + _input = "10.10.1[67].0/24" + with pytest.raises(cidrize.CidrizeError): + self.test(_input) + class TestDump(unittest.TestCase): def test_dump(self): - cidr = cidrize.cidrize('1.2.3.*') + cidr = cidrize.cidrize("1.2.3.*") result = cidrize.dump(cidr) - self.assertEqual(str, type(result)) + assert isinstance(result, str) + class TestOutputStr(unittest.TestCase): def test_output_str(self): - cidr = cidrize.cidrize('10.20.30.40-50', strict=True) - sep = ', ' - expected = '10.20.30.40/29, 10.20.30.48/31, 10.20.30.50/32' - self.assertEqual(expected, cidrize.output_str(cidr, sep)) + cidr = cidrize.cidrize("10.20.30.40-50", strict=True) + sep = ", " + expected = "10.20.30.40/29, 10.20.30.48/31, 10.20.30.50/32" + assert expected == cidrize.output_str(cidr, sep) def test_range6(self): - cidr = cidrize.cidrize('2001:1234::0.0.64.0-2001:1234::FFff') - sep = ', ' - expected = '2001:1234::4000/114, 2001:1234::8000/113' - self.assertEqual(expected, cidrize.output_str(cidr, sep)) + cidr = cidrize.cidrize("2001:1234::0.0.64.0-2001:1234::FFff") + sep = ", " + expected = "2001:1234::4000/114, 2001:1234::8000/113" + assert expected == cidrize.output_str(cidr, sep) class TestOptimizeNetworkRange(unittest.TestCase): @@ -140,21 +157,19 @@ def setUp(self): self.test = cidrize.optimize_network_range def test_glob_style(self): - expected = [IPNetwork('10.181.25.0/24')] - _input = '10.181.25.*' - self.assertEqual(expected, self.test(_input)) + expected = [IPNetwork("10.181.25.0/24")] + _input = "10.181.25.*" + assert expected == self.test(_input) + -''' +""" class TestParseArgs(unittest.TestCase): def test_parse_args(self): - # self.assertEqual(expected, parse_args(argv)) + # assert expected == parse_args(argv)) assert False # TODO: implement your test here class TestMain(unittest.TestCase): def test_main(self): - # self.assertEqual(expected, main()) + # assert expected == main()) assert False # TODO: implement your test here -''' - -if __name__ == '__main__': - unittest.main() +""" From a33de243281211b229fe75c45405f3fa083fb0e4 Mon Sep 17 00:00:00 2001 From: jathanism Date: Tue, 30 Mar 2021 21:48:24 -0700 Subject: [PATCH 2/7] Cleanup old files and include license file --- MANIFEST.in | 3 -- examples/parse_ip | 71 ---------------------------------- pyproject.toml | 4 +- setup.py | 97 ----------------------------------------------- 4 files changed, 2 insertions(+), 173 deletions(-) delete mode 100644 MANIFEST.in delete mode 100644 examples/parse_ip delete mode 100755 setup.py diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 21f823c..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1,3 +0,0 @@ -include examples/* -include CHANGELOG.rst -include TODO diff --git a/examples/parse_ip b/examples/parse_ip deleted file mode 100644 index d793643..0000000 --- a/examples/parse_ip +++ /dev/null @@ -1,71 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -""" -A script using cidrize to output IP addresses with errors/status for use with an AJAX form -that is expected to strictly validate IP addresses. -""" - -__version__ = '0.1' -__author__ = 'Jathan McCollum ' - -from cidrize import cidrize, output_str -from netaddr import spanning_cidr -from optparse import OptionParser -import re -import sys - -_SELF = sys.argv[0] -DEBUG = False - - -def validate_cidr(cidr): - """ - Made for use with jquery.autocomplete. Two matches are returned separated by newlines - in the format "network|status": - - 1. The precise calculated networks based on the input range, (Strict) - 2. The maximum spanning CIDR between the first and last IPs. (Loose) - - Example: - input: - 16.17.18.19-29 - - output: - 16.17.18.19/32, 16.17.18.20/30, 16.17.18.24/30, 16.17.18.28/31|ok - 16.17.18.16/28|ok - """ - mycidr = output_str(cidr) - if not re.match(r'^\d+', mycidr): - return "|%s" % mycidr - return mycidr + "|ok" - -def output_both(cidr, validate=False): - if validate: - func = validate_cidr - else: - func = output_str - - ret = func(cidr) - if len(cidr) > 1: - ret += '\n' + func([spanning_cidr(cidr)]) - - return ret - -def main(): - ipaddr = [] - try: - ipaddr = sys.argv[1] - except IndexError: - print "usage: %s 1.2.3.4/32" % _SELF - sys.exit(-1) - - try: - cidr = cidrize(ipaddr, modular=False) - if cidr: - print output_both(cidr, validate=True) - except IndexError, err: - sys.exit(-1) - -if __name__ == '__main__': - main() diff --git a/pyproject.toml b/pyproject.toml index 14f43be..b1dba87 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ name = "cidrize" version = "2.0.0" description = "Cidrize parses IPv4/IPv6 addresses, CIDRs, ranges, and wildcard matches & attempts to return a valid list of IP addresses" authors = ["Jathan McCollum "] -license = "Apache-2.0" +license = "BSD-3-Clause" readme = "README.rst" keywords = [ "Networking", @@ -53,8 +53,8 @@ packages = [ ] include = [ "CHANGELOG.rst", + "LICENSE.rst", "TODO", - "examples/*", ] [tool.poetry.dependencies] diff --git a/setup.py b/setup.py deleted file mode 100755 index 76ee2f4..0000000 --- a/setup.py +++ /dev/null @@ -1,97 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -from __future__ import print_function - -try: - from setuptools import setup, Command -except ImportError: - from distutils.core import setup, Command -import os -import sys -import unittest - -#from cidrize import __version__ -__version__ = '0.7.0' - -if sys.version_info[:2] < (2, 6): - print("This package requires Python 2.6+. Sorry!") - sys.exit(-1) - -class CleanCommand(Command): - description = "cleans up non-package files. (dist, build, etc.)" - user_options = [] - def initialize_options(self): - self.files = None - def finalize_options(self): - self.files = './build ./dist ./MANIFEST ./*.pyc ./*.egg-info' - def run(self): - #files = './build ./dist ./MANIFEST ./*.pyc' - print('Cleaning: %s' % self.files) - os.system('rm -rf ' + self.files) - -class TestCommand(Command): - description = 'run unit tests' - user_options = [] - def initialize_options(self): - pass - def finalize_options(self): - pass - def run(self): - args = [unittest.__file__] - for root, dirs, files in os.walk('tests'): - for file in files: - if file.startswith('test') and file.endswith('py'): - args.append(file[:-3]) - sys.path.append('tests') - unittest.main(None, None, args) - -setup( - name = 'cidrize', - version = __version__, - url = 'http://github.com/jathanism/cidrize/', - license = 'BSD', - description = "Cidrize parses IPv4/IPv6 addresses, CIDRs, ranges, and wildcard matches & attempts return a valid list of IP addresses", - author = 'Jathan McCollum', - author_email = 'jathan@gmail.com', - py_modules = ['cidrize'], - scripts = ['scripts/cidr'], - install_requires=['netaddr>=0.7.6',], - tests_require=['netaddr>=0.7.6', ], - keywords = [ - 'Networking', 'Systems Administration', 'IANA', 'IEEE', 'CIDR', 'IP', - 'IPv4', 'IPv6', 'IP Address', 'Firewalls', 'Security', - ], - classifiers = [ - 'Development Status :: 4 - Beta', - 'Environment :: Console', - 'Environment :: Plugins', - 'Intended Audience :: Developers', - 'Intended Audience :: Education', - 'Intended Audience :: Information Technology', - 'Intended Audience :: Science/Research', - 'Intended Audience :: System Administrators', - 'Intended Audience :: Telecommunications Industry', - 'Natural Language :: English', - 'License :: OSI Approved :: BSD License', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Programming Language :: Python :: 2.6', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Topic :: Education :: Testing', - 'Topic :: Internet', - 'Topic :: Internet :: Name Service (DNS)', - 'Topic :: Software Development', - 'Topic :: Software Development :: Libraries :: Python Modules', - 'Topic :: System :: Networking', - 'Topic :: System :: Networking :: Firewalls', - 'Topic :: System :: Networking :: Monitoring', - 'Topic :: System :: Operating System', - 'Topic :: System :: Systems Administration', - 'Topic :: Utilities', - ], - cmdclass = { - 'clean': CleanCommand, - 'test': TestCommand, - } -) From c1c19324960f68fb8cc7d0a9a7477a0644a77ea2 Mon Sep 17 00:00:00 2001 From: jathanism Date: Tue, 30 Mar 2021 22:01:49 -0700 Subject: [PATCH 3/7] Ship it. --- CHANGELOG.rst | 12 ++++++++---- cidrize.py | 20 +++++++++++--------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 3d4478b..12bb036 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,9 +2,13 @@ Changelog ========= -0.7.0 - -- Python3 support via six -- Python2.6 required +2.0.0 - 2021-03-03 +================== + +- Python3 required +- Python2 dropped +- Implemented black and pytest and pylint +- Switched from setuptools + ``setup.py`` to Poetry + ``pyproject.toml`` 0.6.5 - 2015-08-11 ================== @@ -41,7 +45,7 @@ Changelog - Other housekeeping within setup.py 0.6 - 2012-04-10 -================== +================ - IPv6 addresses are now supported! - Enhanced dump output to display IP version diff --git a/cidrize.py b/cidrize.py index cb1122e..6349e1c 100755 --- a/cidrize.py +++ b/cidrize.py @@ -252,7 +252,7 @@ def is_ipv6(ipstr): return False -def cidrize(ipstr, strict=False, modular=True): +def cidrize(ipstr, strict=False, raise_errors=True): """ This function tries to determine the best way to parse IP addresses correctly & has all the logic for trying to do the right thing! @@ -283,18 +283,19 @@ def cidrize(ipstr, strict=False, modular=True): Defaults: - * parsing exceptions will raise a CidrizeError (modular=True). + * parsing exceptions will raise a CidrizeError (raise_errors=True). * results will be returned as a spanning CIDR (strict=False). :param ipstr: IP string to be parsed. - :param modular: + :param raise_errors: Set to False to cause exceptions to be stripped & the error text will be returned as a list. This is intended for use with scripts or APIs out-of-the box. - Example: + Example:: + >>> import cidrize as c >>> c.cidrize('1.2.3.4-1.2.3.1099') Traceback (most recent call last): @@ -303,13 +304,14 @@ def cidrize(ipstr, strict=False, modular=True): raise CidrizeError(err) cidrize.CidrizeError: base address '1.2.3.1099' is not IPv4 - >>> c.cidrize('1.2.3.4-1.2.3.1099', modular=False) + >>> c.cidrize('1.2.3.4-1.2.3.1099', raise_errors=False) ["base address '1.2.3.1099' is not IPv4"] :param strict: Set to True to return explicit networks based on start/end addresses. - Example: + Example:: + >>> import cidrize as c >>> c.cidrize('1.2.3.4-1.2.3.10') [IPNetwork('1.2.3.0/28')] @@ -322,7 +324,7 @@ def cidrize(ipstr, strict=False, modular=True): # Short-circuit to parse commas since it calls back here anyway if "," in ipstr: - return parse_commas(ipstr, strict=strict, modular=modular) + return parse_commas(ipstr, strict=strict, raise_errors=raise_errors) # Short-circuit for hostnames (we're assuming first char is alpha) if RE_HOSTNAME.match(ipstr): @@ -404,7 +406,7 @@ def cidrize(ipstr, strict=False, modular=True): return [result.cidr] # IPNetwork has .cidr except (AddrFormatError, TypeError, ValueError) as err: - if modular: + if raise_errors: raise CidrizeError(err) return [str(err)] @@ -644,7 +646,7 @@ def main(): ipstr = opts.ip try: - cidr = cidrize(ipstr, modular=False) + cidr = cidrize(ipstr, raise_errors=False) if cidr: if opts.verbose: print(dump(cidr)) From 954d450f5c5ae574b5de7e0e1ba255a5bc99d7e4 Mon Sep 17 00:00:00 2001 From: jathanism Date: Tue, 30 Mar 2021 22:20:26 -0700 Subject: [PATCH 4/7] Make pylint happy and add `.travis.yml` --- .travis.yml | 19 +++++++++++++++++++ cidrize.py | 20 ++++++++++---------- pyproject.toml | 2 +- 3 files changed, 30 insertions(+), 11 deletions(-) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..4c414fa --- /dev/null +++ b/.travis.yml @@ -0,0 +1,19 @@ +language: python +matrix: + include: + - python: "3.6" + - python: "3.7" + - python: "3.8" + - python: "3.9" +before_install: + - pip install poetry +install: + - poetry config virtualenvs.create false + # Poetry 1.1.0 added parallel installation as an option; + # unfortunately it seems to have some issues with installing/updating "requests" and "certifi" + # while simultaneously atttempting to *use* those packages to install other packages. + # For now we disable it. + - poetry config installer.parallel false + - poetry install +script: + - poetry run pytest diff --git a/cidrize.py b/cidrize.py index 6349e1c..af61bab 100755 --- a/cidrize.py +++ b/cidrize.py @@ -16,6 +16,7 @@ import itertools import logging +from optparse import OptionParser import os import re import socket @@ -94,7 +95,7 @@ "CidrizeError", "dump", "normalize_address", - "optimize_network_usage", + "optimize_network_range", "parse_range", "is_ipv6", ) @@ -244,7 +245,7 @@ def is_ipv6(ipstr): :param ipstr: A suspected IPv6 address """ - log.debug("is_ipv6() got: %r" % ipstr) + log.debug("is_ipv6() got: %r", ipstr) try: socket.inet_pton(socket.AF_INET6, ipstr) return True @@ -320,7 +321,7 @@ def cidrize(ipstr, strict=False, raise_errors=True): [IPNetwork('1.2.3.4/30'), IPNetwork('1.2.3.8/31'), IPNetwork('1.2.3.10/32')] """ - ip = None + ipobj = None # Short-circuit to parse commas since it calls back here anyway if "," in ipstr: @@ -342,8 +343,8 @@ def cidrize(ipstr, strict=False, raise_errors=True): # Now with IPv6! elif RE_CIDR.match(ipstr) or is_ipv6(ipstr): log.debug("Trying CIDR style...") - ip = IPNetwork(ipstr) - return [ip.cidr] + ipobj = IPNetwork(ipstr) + return [ipobj.cidr] # Parse 1.2.3.118-1.2.3.121 range style elif RE_RANGE.match(ipstr): @@ -407,7 +408,7 @@ def cidrize(ipstr, strict=False, raise_errors=True): except (AddrFormatError, TypeError, ValueError) as err: if raise_errors: - raise CidrizeError(err) + raise CidrizeError(str(err)) from err return [str(err)] @@ -498,8 +499,8 @@ def normalize_address(ipstr): return ipstr octets = (int(i) for i in myip.split(".")) - ip = ".".join([str(o) for o in octets]) - return "{0}/{1}".format(ip, cidr) + ipobj = ".".join([str(o) for o in octets]) + return "{0}/{1}".format(ipobj, cidr) def netaddr_to_ipy(iplist): @@ -512,7 +513,7 @@ def netaddr_to_ipy(iplist): A list of netaddr.IPNetwork objects. """ try: - import IPy + import IPy # pylint: disable=import-outside-toplevel except ImportError: return iplist @@ -567,7 +568,6 @@ def dump(cidr): def parse_args(argv): """Parses args.""" - from optparse import OptionParser parser = OptionParser( usage="%prog [-v] [-d] [ip network]", diff --git a/pyproject.toml b/pyproject.toml index b1dba87..d77b78d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -99,7 +99,7 @@ exclude = ''' [tool.pytest.ini_options] minversion = "6.0" -addopts = "-vv" +addopts = "-vv --black --pylint --pylint-error-types CEFW" testpaths = [ "tests", ] From ccb1a398053020071ec8a91b4346077b47a6c677 Mon Sep 17 00:00:00 2001 From: jathanism Date: Tue, 30 Mar 2021 22:36:11 -0700 Subject: [PATCH 5/7] Make pylint super happy (warnings) --- .travis.yml | 2 +- cidrize.py | 13 +++++++------ pyproject.toml | 13 ++++++++++++- tests/test_cidrize.py | 26 ++++++-------------------- 4 files changed, 26 insertions(+), 28 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4c414fa..4d43d36 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,6 +14,6 @@ install: # while simultaneously atttempting to *use* those packages to install other packages. # For now we disable it. - poetry config installer.parallel false - - poetry install + - poetry install --no-ansi script: - poetry run pytest diff --git a/cidrize.py b/cidrize.py index af61bab..7c107b3 100755 --- a/cidrize.py +++ b/cidrize.py @@ -16,7 +16,7 @@ import itertools import logging -from optparse import OptionParser +from optparse import OptionParser # pylint: disable=deprecated-module import os import re import socket @@ -452,15 +452,16 @@ def optimize_network_range(ipstr, threshold=0.9, verbose=DEBUG): strict = IPSet(cidrize(ipstr, strict=True)) ratio = float(len(strict)) / float(len(loose)) - verbose and print( - "Subnet usage ratio: %s; Threshold: %s" % (ratio, threshold) - ) + if verbose: + print("Subnet usage ratio: %s; Threshold: %s" % (ratio, threshold)) if ratio >= threshold: - verbose and print("Over threshold, IP Parse Mode: LOOSE") + if verbose: + print("Over threshold, IP Parse Mode: LOOSE") result = loose.iter_cidrs() else: - verbose and print("Under threshold, IP Parse Mode: STRICT") + if verbose: + print("Under threshold, IP Parse Mode: STRICT") result = strict.iter_cidrs() return result diff --git a/pyproject.toml b/pyproject.toml index d77b78d..bb57103 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -99,7 +99,18 @@ exclude = ''' [tool.pytest.ini_options] minversion = "6.0" -addopts = "-vv --black --pylint --pylint-error-types CEFW" +addopts = "-vv --black --pylint --pylint-error-types CEFw" testpaths = [ "tests", ] + +[tool.pylint.messages_control] +# Line length is enforced by Black, so pylint doesn't need to check it. +# Pylint and Black disagree about how to format multi-line arrays; Black wins. +disable = """, + missing-class-docstring, + """ + +[tool.pylint.basic] +# No docstrings required for private methods (Pylint default), or for test_ functions, or for inner Meta classes. +no-docstring-rgx="^(_|test_|Meta$)" diff --git a/tests/test_cidrize.py b/tests/test_cidrize.py index 2a1dee1..8de9619 100755 --- a/tests/test_cidrize.py +++ b/tests/test_cidrize.py @@ -1,12 +1,10 @@ -#!/usr/bin/env python +""" +Unit tests for ``cidrize``. +""" import unittest -from netaddr import ( - IPRange, - IPAddress, - IPNetwork, -) +from netaddr import IPRange, IPNetwork import pytest import cidrize @@ -31,7 +29,8 @@ def setUp(self): def test_everything_style(self): expected = set([IPNetwork("0.0.0.0/0")]) _input = set() - [_input.add(self.test(item)[0]) for item in cidrize.EVERYTHING] + for item in cidrize.EVERYTHING: + _input.add(self.test(item)[0]) assert expected == _input def test_cidr_style_ipv4(self): @@ -160,16 +159,3 @@ def test_glob_style(self): expected = [IPNetwork("10.181.25.0/24")] _input = "10.181.25.*" assert expected == self.test(_input) - - -""" -class TestParseArgs(unittest.TestCase): - def test_parse_args(self): - # assert expected == parse_args(argv)) - assert False # TODO: implement your test here - -class TestMain(unittest.TestCase): - def test_main(self): - # assert expected == main()) - assert False # TODO: implement your test here -""" From 7f4ffc8714b0548fe702f9549bbd94798b9932dc Mon Sep 17 00:00:00 2001 From: jathanism Date: Tue, 30 Mar 2021 22:37:24 -0700 Subject: [PATCH 6/7] Remove python 3.6 from Travis --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 4d43d36..de71471 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ language: python matrix: include: - - python: "3.6" - python: "3.7" - python: "3.8" - python: "3.9" From f106aa3287a44076ff30e25108000a6438d0a71e Mon Sep 17 00:00:00 2001 From: jathanism Date: Tue, 30 Mar 2021 22:48:40 -0700 Subject: [PATCH 7/7] Revise README for clarity. --- README.rst | 42 ++++++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/README.rst b/README.rst index ef324e1..85b89d9 100644 --- a/README.rst +++ b/README.rst @@ -1,12 +1,35 @@ ======= -CIDRIZE +Cidrize ======= -Intelligently parse IPv4/IPv6 addresses, CIDRs, ranges, and wildcard matches to +IP address parsing for humans. + +Cidrize takes IP address inputs that people tend to use in practice, validates +them, and converts them to objects. + +Intelligently parses IPv4/IPv6 addresses, CIDRs, ranges, and wildcard matches to attempt return a valid list of IP addresses. The ``cidrize()`` function does all the work trying to parse IP addresses correctly. +============ +Installation +============ + +You can install ``cidrize`` via Pip:: + + pip install cidrize + +============ +Dependencies +============ + +Cidrize is basically a thin veneer around `netaddr `_ to provide a human layer for parsing IP addresses. + +===== +Usage +===== + Supported input formats ----------------------- @@ -31,23 +54,15 @@ Network mask (e.g. 192.0.2.0 255.255.255.0) and host mask (aka reverse mask, The cidrize function returns a list of consolidated ``netaddr.IPNetwork`` objects. By default parsing exceptions will raise a ``CidrizeError`` (with -default argument of ``modular=True``). You may pass ``modular=False`` to cause +default argument of ``raise_errors=True``). You may pass ``raise_errors=False`` to cause exceptions to be stripped and the error text will be returned as a list. This is intended for use with scripts or APIs where receiving exceptions would not be preferred. The module may also be run as a script for debugging purposes. -============ -Dependencies -============ - -:`netaddr `_: Pythonic manipulation of -IPv4, IPv6, CIDR, EUI and MAC network addresses - -===== -Usage -===== +The cidrize function +-------------------- Fire up your trusty old Python interpreter and follow along! @@ -148,7 +163,6 @@ Verbose output:: And that's that! - ======= License =======