import socket
import argparse
import re


class RegexMatch(object):
    """
    Performs regular expression match on value.
    If the regular expression pattern matches it will it will return an error
    message that will work with argparse.
    """

    def __init__(self, pattern, statement=None):
        self.string_pattern = pattern
        self.pattern = re.compile(pattern)
        self.statement = statement
        if not self.statement:
            self.statement = "must match pattern %s" % self.string_pattern

    def __call__(self, string):
        match = self.pattern.search(string)
        if match:
            raise argparse.ArgumentError(None, self.statement)
        return string


class Hostname(object):
    """
    Checks wether a given hostname is resolvable in DNS, otherwise raising and
    argparse error.
    """

    def __init__(self, _socket=None):
        self.socket = _socket or socket  # just used for testing

    def __call__(self, string):
        parts = string.split(':')
        name = parts[0]
        host = parts[-1]
        try:
            self.socket.gethostbyname(host)
        except self.socket.gaierror:
            msg = "hostname: %s is not resolvable" % host
            raise argparse.ArgumentError(None, msg)

        try:
            self.socket.inet_aton(name)
        except self.socket.error:
            return string  # not an IP
        else:
            msg = '%s must be a hostname not an IP' % name
            raise argparse.ArgumentError(None, msg)

        return string

import socket
import argparse
import re


class RegexMatch(object):
    """
    Performs regular expression match on value.
    If the regular expression pattern matches it will it will return an error
    message that will work with argparse.
    """

    def __init__(self, pattern, statement=None):
        self.string_pattern = pattern
        self.pattern = re.compile(pattern)
        self.statement = statement
        if not self.statement:
            self.statement = "must match pattern %s" % self.string_pattern

    def __call__(self, string):
        match = self.pattern.search(string)
        if match:
            raise argparse.ArgumentError(None, self.statement)
        return string


class Hostname(object):
    """
    Checks wether a given hostname is resolvable in DNS, otherwise raising and
    argparse error.
    """

    def __init__(self, _socket=None):
        self.socket = _socket or socket  # just used for testing

    def __call__(self, string):
        parts = string.split(':')
        name = parts[0]
        host = parts[-1]
        try:
            self.socket.getaddrinfo(host, 0)
        except self.socket.gaierror:
            msg = "hostname: %s is not resolvable" % host
            raise argparse.ArgumentError(None, msg)

        try:
            self.socket.inet_aton(name)
        except self.socket.error:
            return string  # not an IP
        else:
            msg = '%s must be a hostname not an IP' % name
            raise argparse.ArgumentError(None, msg)

        return string

Support hostname that resolve to IPv6-only address
Support hostname that resolve to IPv6-only address

The current hostname validation does not cope with IPv6-only hostnames. Use getaddrinfo instead of gethostbyname to fix this. getaddrinfo raises the same exceptions and should work like a drop-in-replacement in this scenario.

We should also address the IPv4-only check for if then input is an IP-address but the use of split on ':' is problematic and prevents code to check for IPv6-addresses as input. I'm not sure what the thought behind allowing "name:host" is so leaving it untouched.

I have proposed code to check and warn for both IPv4 and IPv6 address input that I can provide as soon as I understand the scope of parsing "name:host" input.
  import socket
  import argparse
  import re
  
  
  class RegexMatch(object):
      """
      Performs regular expression match on value.
      If the regular expression pattern matches it will it will return an error
      message that will work with argparse.
      """
  
      def __init__(self, pattern, statement=None):
          self.string_pattern = pattern
          self.pattern = re.compile(pattern)
          self.statement = statement
          if not self.statement:
              self.statement = "must match pattern %s" % self.string_pattern
  
      def __call__(self, string):
          match = self.pattern.search(string)
          if match:
              raise argparse.ArgumentError(None, self.statement)
          return string
  
  
  class Hostname(object):
      """
      Checks wether a given hostname is resolvable in DNS, otherwise raising and
      argparse error.
      """
  
      def __init__(self, _socket=None):
          self.socket = _socket or socket  # just used for testing
  
      def __call__(self, string):
          parts = string.split(':')
          name = parts[0]
          host = parts[-1]
          try:
-             self.socket.gethostbyname(host)
+             self.socket.getaddrinfo(host, 0)
          except self.socket.gaierror:
              msg = "hostname: %s is not resolvable" % host
              raise argparse.ArgumentError(None, msg)
  
          try:
              self.socket.inet_aton(name)
          except self.socket.error:
              return string  # not an IP
          else:
              msg = '%s must be a hostname not an IP' % name
              raise argparse.ArgumentError(None, msg)
  
          return string
  
Support hostname that resolve to IPv6-only address
## Code Before:
import socket
import argparse
import re


class RegexMatch(object):
    """
    Performs regular expression match on value.
    If the regular expression pattern matches it will it will return an error
    message that will work with argparse.
    """

    def __init__(self, pattern, statement=None):
        self.string_pattern = pattern
        self.pattern = re.compile(pattern)
        self.statement = statement
        if not self.statement:
            self.statement = "must match pattern %s" % self.string_pattern

    def __call__(self, string):
        match = self.pattern.search(string)
        if match:
            raise argparse.ArgumentError(None, self.statement)
        return string


class Hostname(object):
    """
    Checks wether a given hostname is resolvable in DNS, otherwise raising and
    argparse error.
    """

    def __init__(self, _socket=None):
        self.socket = _socket or socket  # just used for testing

    def __call__(self, string):
        parts = string.split(':')
        name = parts[0]
        host = parts[-1]
        try:
            self.socket.gethostbyname(host)
        except self.socket.gaierror:
            msg = "hostname: %s is not resolvable" % host
            raise argparse.ArgumentError(None, msg)

        try:
            self.socket.inet_aton(name)
        except self.socket.error:
            return string  # not an IP
        else:
            msg = '%s must be a hostname not an IP' % name
            raise argparse.ArgumentError(None, msg)

        return string

## Instruction:
Support hostname that resolve to IPv6-only address
## Code After:
import socket
import argparse
import re


class RegexMatch(object):
    """
    Performs regular expression match on value.
    If the regular expression pattern matches it will it will return an error
    message that will work with argparse.
    """

    def __init__(self, pattern, statement=None):
        self.string_pattern = pattern
        self.pattern = re.compile(pattern)
        self.statement = statement
        if not self.statement:
            self.statement = "must match pattern %s" % self.string_pattern

    def __call__(self, string):
        match = self.pattern.search(string)
        if match:
            raise argparse.ArgumentError(None, self.statement)
        return string


class Hostname(object):
    """
    Checks wether a given hostname is resolvable in DNS, otherwise raising and
    argparse error.
    """

    def __init__(self, _socket=None):
        self.socket = _socket or socket  # just used for testing

    def __call__(self, string):
        parts = string.split(':')
        name = parts[0]
        host = parts[-1]
        try:
            self.socket.getaddrinfo(host, 0)
        except self.socket.gaierror:
            msg = "hostname: %s is not resolvable" % host
            raise argparse.ArgumentError(None, msg)

        try:
            self.socket.inet_aton(name)
        except self.socket.error:
            return string  # not an IP
        else:
            msg = '%s must be a hostname not an IP' % name
            raise argparse.ArgumentError(None, msg)

        return string
