from enum import Enum
from ipaddress import (
IPV4LENGTH,
IPV6LENGTH,
AddressValueError,
IPv4Address,
IPv4Network,
IPv6Address,
IPv6Network,
ip_address,
ip_network,
)
from typing import Literal, Optional, Union
IPVersion = Literal["ipv4", "ipv6"]
class LocalhostIP(str, Enum):
ipv4 = "127.0.0.1"
ipv6 = "::1"
def __str__(self):
return self.value
class NumericIPVersion(int, Enum):
"""Example: (IPListRecord.version==NumericIPVersion[ip_version])"""
ipv4 = 4
ipv6 = 6
def __str__(self):
return str(self.value)
@classmethod
def from_ip_version(
cls, ip_version: Optional[IPVersion]
) -> Optional["NumericIPVersion"]:
if ip_version is None:
return None
return cls.ipv4 if ip_version == IP.V4 else cls.ipv6
def is_valid_ipv4_addr(addr):
return IP.is_valid_ipv4_addr(addr)
def is_valid_ipv4_network(addr, strict=False):
return IP.is_valid_ipv4_network(addr, strict)
class IP:
V4: IPVersion = "ipv4"
V6: IPVersion = "ipv6"
@classmethod
def check_ip_ver(cls, version):
return any(version == ver for ver in [cls.V4, cls.V6])
@classmethod
def is_valid_ip(cls, addr):
try:
ip_address(addr)
except ValueError:
return False
return True
@classmethod
def is_valid_ip_network(cls, *args, **kwargs):
return cls.is_valid_ipv4_network(
*args, **kwargs
) or cls.is_valid_ipv6_network(*args, **kwargs)
@classmethod
def is_valid_ipv4_addr(cls, addr):
try:
IPv4Address(addr)
except AddressValueError:
return False
return True
@classmethod
def is_valid_ipv6_addr(cls, addr):
try:
IPv6Address(addr)
except AddressValueError:
return False
return True
@classmethod
def is_valid_ipv4_network(
cls, addr: Union[str, IPv4Network, IPv6Network], strict=False
):
try:
ip = IPv4Network(addr)
except ValueError:
return False
if strict:
# IPV4LENGTH - netmask for host
return ip.prefixlen != IPV4LENGTH
return True
@classmethod
def is_valid_ipv6_network(
cls, addr: Union[str, IPv4Network, IPv6Network], strict=False
):
try:
ip = IPv6Network(addr)
except ValueError:
return False
if strict:
# IPV6LENGTH - netmask for host
return ip.prefixlen != IPV6LENGTH
return True
@classmethod
def type_of(cls, addr):
if cls.is_valid_ipv4_network(addr):
return IP.V4
elif cls.is_valid_ipv6_network(addr):
return IP.V6
raise ValueError("Invalid ip address")
@classmethod
def convert_to_ipv6_network(cls, ip, mask="/64"):
"""Conver ipv6 addr to ipv6 network with mask
:param str ip: ip for converting
:param str mask: ip network mask
"""
network = IPv6Network(ip + mask, strict=False)
return str(network)
@staticmethod
def adopt_to_ipvX_network(
ip_arg: Union[str, IPv4Address, IPv4Network, IPv6Address, IPv6Network]
) -> Union[IPv4Network, IPv6Network]:
"""
Eliminate str from the Union
:raise ValueError: if cannot convert ip_arg str to ip network
"""
if isinstance(ip_arg, (IPv4Network, IPv6Network)):
return ip_arg
elif isinstance(ip_arg, (IPv4Address, IPv6Address)):
prefixlen = IPV4LENGTH if ip_arg.version == 4 else IPV6LENGTH
return ip_network((int(ip_arg), prefixlen))
return ip_network(ip_arg)
@classmethod
def ip_net_to_string(cls, net: Union[IPv4Network, IPv6Network]) -> str:
"""
IPv4Network('192.168.1.1/32') -> '192.168.1.1'
IPv4Network('192.168.1.0/24') -> '192.168.1.0/24'
"""
if not int(net.hostmask):
return str(net.network_address)
return str(net)
@classmethod
def ipv6_to_64network(
cls, ip: Union[IPv4Address, IPv6Address, str]
) -> Union[IPv4Address, IPv6Network]:
if isinstance(ip, IPv6Address):
return IPv6Network(cls.convert_to_ipv6_network(str(ip)))
return ip