Repository: lk-geimfari/mimesis

Issue:
Improve IPv4/IPv6 generators for Internet()
We can improve how `.ip_v4()` and `.ip_v6()` works.

I suggest using `ipaddress` module from the standard library, just like this:

```python
    def ip_v4(self, with_port: bool = False, stringify: bool = True) -> Union[str, IPv4Address]:
        """Generate a random IPv4 address.

        :param stringify:  Return IPv4 as string (True by default).
        :param with_port: Add port to IP.
        :return: Random IPv4 address.

        :Example:
            19.121.223.58
        """
        ipv4 = IPv4Address(self.random.getrandbits(32))

        if not stringify:
            return ipv4

        if with_port:
            ipv4 += ':{}'.format(self.port())

        return ipv4
```

The idea is to support IP addresses as objects to make them more useful than just strings.


Discussion:
I can make class `IPv4Gen` with methods like: 

```
• .get_mask() 
• .set_public()
• .set_local(owner) - owner is IPv4Gen obj
• .add_DN(domain) - Domain name 
• .set_route() 
• .add_to_route_list(ip)
``` 

Give me response about my idea

Resolving patch:
diff --git a/mimesis/providers/internet.py b/mimesis/providers/internet.py
--- a/mimesis/providers/internet.py
+++ b/mimesis/providers/internet.py
@@ -4,7 +4,7 @@
 
 import urllib.error
 import urllib.request
-from ipaddress import IPv6Address
+from ipaddress import IPv4Address, IPv6Address
 from typing import List, Optional, Union
 
 from mimesis.data import (
@@ -37,6 +37,8 @@ def __init__(self, *args, **kwargs):
         """
         super().__init__(*args, **kwargs)
         self.__file = File(seed=self.seed)
+        self._MAX_IPV4 = (2 ** 32) - 1
+        self._MAX_IPV6 = (2 ** 128) - 1
 
     class Meta:
         """Class for metadata."""
@@ -84,36 +86,60 @@ def http_method(self) -> str:
         """
         return self.random.choice(HTTP_METHODS)
 
-    def ip_v4(self, with_port: bool = False) -> str:
-        """Generate a random IPv4 address.
+    def ip_v4_object(self) -> IPv4Address:
+        """Generate random IPv4Address object.
 
-        :param with_port: Add port to IP.
-        :return: Random IPv4 address.
+        See documentation for module ipaddress:
+        https://docs.python.org/3.7/library/ipaddress.html
+
+        :return: IPv4Address object.
+        """
+        return IPv4Address(
+            self.random.randint(0, self._MAX_IPV4),
+        )
+
+    def ip_v6_object(self) -> IPv6Address:
+        """Generate random IPv6Address object.
+
+        See documentation for module ipaddress:
+        https://docs.python.org/3.7/library/ipaddress.html
+
+        :return: IPv6Address object.
+        """
+        return IPv6Address(
+            self.random.randint(
+                0, self._MAX_IPV6,
+            ),
+        )
+
+    def ip_v4(self, with_port: bool = False,
+              port_range: PortRange = PortRange.ALL) -> str:
+        """Generate a random IPv4 address as string.
+
+        :param port_range: PortRange enum object.
+        :param with_port: Add port from PortRange to IP.
+        :return: IPv4 address as string.
 
         :Example:
-            19.121.223.58
+            19.121.223.58 or 19.121.223.58:8000
         """
-        ip = '.'.join(str(self.random.randint(0, 255)) for _ in range(4))
+        ip = str(self.ip_v4_object())
 
         if with_port:
-            ip += ':{}'.format(self.port())
+            port = self.port(port_range=port_range)
+            return '{}:{}'.format(ip, port)
 
         return ip
 
     def ip_v6(self) -> str:
-        """Generate a random IPv6 address.
+        """Generate a random IPv6 address as string.
 
-        :return: Random IPv6 address.
+        :return: IPv6 address string.
 
         :Example:
             2001:c244:cf9d:1fb1:c56d:f52c:8a04:94f3
         """
-        ipv6 = IPv6Address(
-            self.random.randint(
-                0, 2 ** 128 - 1,
-            ),
-        )
-        return str(ipv6)
+        return str(self.ip_v6_object())
 
     def mac_address(self) -> str:
         """Generate a random MAC address.
@@ -260,7 +286,7 @@ def network_protocol(self, layer: Optional[Layer] = None) -> str:
     def port(self, port_range: PortRange = PortRange.ALL) -> int:
         """Generate random port.
 
-        :param port_range: Range enum object.
+        :param port_range: PortRange enum object.
         :return: Port number.
         :raises NonEnumerableError: if port_range is not in PortRange.
 


Test patch:
diff --git a/tests/test_providers/test_internet.py b/tests/test_providers/test_internet.py
--- a/tests/test_providers/test_internet.py
+++ b/tests/test_providers/test_internet.py
@@ -1,6 +1,7 @@
 # -*- coding: utf-8 -*-
 
 import re
+from ipaddress import IPv4Address, IPv6Address
 
 import pytest
 
@@ -77,12 +78,38 @@ def test_network_protocol_exception(self, net):
         with pytest.raises(NonEnumerableError):
             net.network_protocol(layer='nil')
 
-    def test_ip_v4(self, net):
-        ip = net.ip_v4()
-        assert re.match(patterns.IP_V4_REGEX, ip)
-        ip_with_port = net.ip_v4(with_port=True)
-        port = int(ip_with_port.split(':')[1])
-        assert (port >= 1) and (port <= 65535)
+    def test_ip_v4_object(self, net):
+        ip = net.ip_v4_object()
+        assert ip.version == 4
+        assert ip.max_prefixlen == 32
+        assert re.match(patterns.IP_V4_REGEX, ip.exploded)
+        assert isinstance(ip, IPv4Address)
+
+    @pytest.mark.parametrize(
+        'with_port, port_range', [
+            (False, PortRange.ALL),
+            (True, PortRange.ALL),
+            (True, PortRange.WELL_KNOWN),
+            (True, PortRange.EPHEMERAL),
+            (True, PortRange.REGISTERED),
+        ],
+    )
+    def test_ip_v4(self, net, with_port, port_range):
+        ip = net.ip_v4(with_port, port_range)
+
+        if not with_port:
+            assert re.match(patterns.IP_V4_REGEX, ip)
+        else:
+            port = int(ip.split(':')[-1])
+            port_start, port_end = port_range.value
+            assert port_start <= port <= port_end
+
+    def test_ip_v6_object(self, net):
+        ip = net.ip_v6_object()
+        assert ip.version == 6
+        assert ip.max_prefixlen == 128
+        assert re.match(patterns.IP_V6_REGEX, ip.exploded)
+        assert isinstance(ip, IPv6Address)
 
     def test_ip_v6(self, net):
         ip = net.ip_v6()
@@ -193,10 +220,18 @@ def test_network_protocol(self, i1, i2):
     def test_ip_v4(self, i1, i2):
         assert i1.ip_v4() == i2.ip_v4()
         assert i1.ip_v4(with_port=True) == i2.ip_v4(with_port=True)
+        assert i1.ip_v4(with_port=True, port_range=PortRange.ALL) == \
+               i2.ip_v4(with_port=True, port_range=PortRange.ALL)
+
+    def test_ip_v4_object(self, i1, i2):
+        assert i1.ip_v4_object() == i2.ip_v4_object()
 
     def test_ip_v6(self, i1, i2):
         assert i1.ip_v6() == i2.ip_v6()
 
+    def test_ip_v6_object(self, i1, i2):
+        assert i1.ip_v6_object() == i2.ip_v6_object()
+
     def test_mac_address(self, i1, i2):
         assert i1.mac_address() == i2.mac_address()