Repository: joke2k/faker
Pull request #328 (2016-02-19 14:00:36)

Add a binary blob generator.


Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 90b14687..f1d80fd3 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -4,7 +4,7 @@ Changelog
 `0.5.4` - TBD
 -------------
 
-* Expose Provider's random instance. Thank @gsingers for the suggestion. 
+* Expose Provider's random instance. Thank @gsingers for the suggestion.
 * Make sure required characters are in the password. Thanks @craig552uk.
 * Add `internet` and `jobs` Providers for `fa_IR`. Thanks @hamidfzm.
 * Correct Poland phone numbers. Thanks @fizista.
@@ -24,6 +24,9 @@ Changelog
 * Add Maestro credit card. Thanks @anthonylauzon.
 * Add ``hr_HR`` localization. Thanks @mislavcimpersak.
 * Update ``de_DE`` first names. Thanks @WarrenFaith and @mschoebel.
+* Allow generation of IPv4 and IPv6 network address with valid CIDR. Thanks @kdeldycke.
+* Unittest IPv4 and IPv6 address and network generation. Thanks @kdeldycke.
+* Add a new provider to generate random binary blob. Thanks @kdeldycke.
 
 `0.5.3 - 21-September-2015 <http://github.com/joke2k/faker/compare/v0.5.2...v0.5.3>`__
 --------------------------------------------------------------------------------------
@@ -74,7 +77,7 @@ Changelog
 * Added Finnish ssn (HETU) provider. Thanks @kivipe.
 * Fixed person names for ``pl_PL``. Thanks Marek Bleschke.
 * Added ``sv_SE`` locale providers. Thanks Tome Cvitan.
-* ``pt_BR`` Provider: Added ``catch_phrase`` to Company provider and fixed names in Person Provider. Thanks Marcelo Fonseca Tambalo. 
+* ``pt_BR`` Provider: Added ``catch_phrase`` to Company provider and fixed names in Person Provider. Thanks Marcelo Fonseca Tambalo.
 * Added ``sk_SK`` localized providers. Thanks @viktormaruna.
 * Removed ``miscelleneous`` provider. It is superceded by the ``misc`` provider.
 
diff --git a/README.rst b/README.rst
index 5f818b34..f322ec42 100644
--- a/README.rst
+++ b/README.rst
@@ -88,8 +88,10 @@ Providers
 
 Each of the generator properties (like ``name``, ``address``, and
 ``lorem``) are called "fake". A faker generator has many of them,
-packaged in "providers". Here is a list of the bundled formatters in the
-default locale.
+packaged in "providers".
+
+Check the `extended docs`_ for a list of `bundled providers`_ and a list of
+`community providers`_.
 
 Localization
 ------------
@@ -359,6 +361,8 @@ Credits
 .. _Buildout: http://www.buildout.org/
 .. _modern-package-template: http://pypi.python.org/pypi/modern-package-template
 .. _extended docs: http://fake-factory.readthedocs.org/en/latest/
+.. _bundled providers: http://fake-factory.readthedocs.org/en/latest/providers.html
+.. _community providers: http://fake-factory.readthedocs.org/en/latest/communityproviders.html
 .. _LICENSE: https://github.com/joke2k/faker/blob/master/LICENSE.txt
 .. _CONTRIBUTING: https://github.com/joke2k/faker/blob/master/CONTRIBUTING.rst
 
diff --git a/docs/communityproviders.rst b/docs/communityproviders.rst
new file mode 100644
index 00000000..6d3d95e7
--- /dev/null
+++ b/docs/communityproviders.rst
@@ -0,0 +1,12 @@
+.. ref-communityproviders:
+
+Community Providers
+===================
+
+Here's a list of Providers written by the community:
+
++---------------+-------------+----------+
+| Provider name | Description | URL      |
++===============+=============+==========+
+|               |             |          +
++---------------+-------------+----------+
diff --git a/docs/index.rst b/docs/index.rst
index 01d1a171..f34dadaf 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -94,8 +94,10 @@ Providers
 
 Each of the generator properties (like ``name``, ``address``, and
 ``lorem``) are called "fake". A faker generator has many of them,
-packaged in "providers". Here is a list of the bundled formatters in the
-default locale.
+packaged in "providers". For a list of the bundled providers, see
+:doc:`providers`.
+
+For a list of third-party providers, see :doc:`communityproviders`.
 
 Localization
 ------------
@@ -389,6 +391,7 @@ Contents
    :maxdepth: 2
 
    providers
+   communityproviders
    locales
    coding_style
 
diff --git a/faker/cli.py b/faker/cli.py
index 0062574e..23f6e42d 100644
--- a/faker/cli.py
+++ b/faker/cli.py
@@ -38,6 +38,11 @@ def print_provider(doc, provider, formatters, excludes=None, output=None):
             continue
         try:
             lines = text_type(example).expandtabs().splitlines()
+        except UnicodeDecodeError:
+            # The example is actually made of bytes.
+            # We could coerce to bytes, but that would fail anyway when we wiil
+            # try to `print` the line.
+            lines = ["<bytes>"]
         except UnicodeEncodeError:
             raise Exception('error on "{0}" with value "{1}"'.format(
                             signature, example))
diff --git a/faker/providers/internet/__init__.py b/faker/providers/internet/__init__.py
index c4b87fdf..32bc0cf2 100644
--- a/faker/providers/internet/__init__.py
+++ b/faker/providers/internet/__init__.py
@@ -3,10 +3,13 @@
 from __future__ import unicode_literals
 from .. import BaseProvider
 
+from ipaddress import ip_address, ip_network, IPV4LENGTH, IPV6LENGTH
+
 from faker.generator import random
 from faker.providers.lorem.la import Provider as Lorem
 from faker.utils.decorators import slugify, slugify_unicode
 
+
 localized = True
 
 
@@ -99,15 +102,23 @@ class Provider(BaseProvider):
         pattern = self.random_element(self.url_formats)
         return self.generator.parse(pattern)
 
-    def ipv4(self):
-        """
-        Convert 32-bit integer to dotted IPv4 address.
-        """
-        return ".".join(map(lambda n: str(random.randint(-2147483648, 2147483647) >> n & 0xFF), [24, 16, 8, 0]))
-
-    def ipv6(self):
-        res = [hex(random.randint(0, 65535))[2:].zfill(4) for i in range(0, 8)]
-        return ":".join(res)
+    def ipv4(self, network=False):
+        """ Produce a random IPv4 address or network with a valid CIDR. """
+        address = str(ip_address(random.randint(
+            0, (2 ** IPV4LENGTH) - 1)))
+        if network:
+            address += '/' + str(random.randint(0, IPV4LENGTH))
+            address = str(ip_network(address, strict=False))
+        return address
+
+    def ipv6(self, network=False):
+        """ Produce a random IPv6 address or network with a valid CIDR. """
+        address = str(ip_address(random.randint(
+            2 ** IPV4LENGTH, (2 ** IPV6LENGTH) - 1)))
+        if network:
+            address += '/' + str(random.randint(0, IPV6LENGTH))
+            address = str(ip_network(address, strict=False))
+        return address
 
     def mac_address(self):
         mac = [random.randint(0x00, 0xff) for i in range(0, 6)]
diff --git a/faker/providers/misc/__init__.py b/faker/providers/misc/__init__.py
index 9b318691..2cfecb12 100644
--- a/faker/providers/misc/__init__.py
+++ b/faker/providers/misc/__init__.py
@@ -4,6 +4,7 @@ from __future__ import unicode_literals
 import hashlib
 import string
 import uuid
+import os
 
 from faker.generator import random
 from faker.providers.date_time import Provider as DatetimeProvider
@@ -26,6 +27,14 @@ class Provider(BaseProvider):
             -1: False
         }[random.randint(-1, 1)]
 
+    @classmethod
+    def binary(cls, length=(1 * 1024 * 1024)):
+        """ Returns random binary blob.
+
+        Default blob size is 1 Mb.
+        """
+        return os.urandom(length)
+
     @classmethod
     def md5(cls, raw_output=False):
         """
diff --git a/setup.py b/setup.py
index e2767e3a..e4045a03 100644
--- a/setup.py
+++ b/setup.py
@@ -37,6 +37,8 @@ setup(
         'Intended Audience :: Developers',
         'Programming Language :: Python',
         'Programming Language :: Python :: 2',
+        'Programming Language :: Python :: 2.6',
+        'Programming Language :: Python :: 2.7',
         'Programming Language :: Python :: 3',
         'Programming Language :: Python :: 3.4',
         'Topic :: Software Development :: Libraries :: Python Modules',
@@ -58,7 +60,18 @@ setup(
         "six",
     ],
     extras_require={
-        ':python_version=="2.6"': ['importlib'],
-        ':python_version=="3.0"': ['importlib'],
+        ':python_version=="2.6"': [
+            'importlib',
+            'ipaddress',
+        ],
+        ':python_version=="2.7"': [
+            'ipaddress',
+        ],
+        ':python_version=="3.0"': [
+            'importlib',
+        ],
+        ':python_version=="3.2"': [
+            'ipaddress',
+        ],
     }
 )


Test patch:
diff --git a/faker/tests/__init__.py b/faker/tests/__init__.py
index 802bee4c..68129f9d 100644
--- a/faker/tests/__init__.py
+++ b/faker/tests/__init__.py
@@ -7,9 +7,11 @@ __loader__ = None
 import datetime
 import json
 import os
+import re
 import time
 import unittest
 import string
+import six
 import sys
 
 try:
@@ -459,6 +461,16 @@ class FactoryTestCase(unittest.TestCase):
             datetime.datetime.now(utc).replace(second=0, microsecond=0)
         )
 
+    def test_binary(self):
+        from faker.providers.misc import Provider
+
+        for _ in range(999):
+            length = random.randint(0, 2 ** 10)
+            binary = Provider.binary(length)
+
+            self.assertTrue(isinstance(binary, six.binary_type))
+            self.assertTrue(len(binary) == length)
+
     def test_password(self):
         from faker.providers.misc import Provider
 
@@ -535,6 +547,45 @@ class FactoryTestCase(unittest.TestCase):
             email = factory.email()
             self.assertTrue('@' in email)
 
+    def test_ipv4(self):
+        from faker.providers.internet import Provider
+
+        provider = Provider(None)
+
+        for _ in range(999):
+            address = provider.ipv4()
+            self.assertTrue(len(address) >= 7)
+            self.assertTrue(len(address) <= 15)
+            self.assertTrue(
+                re.compile(r'^(\d{1,3}\.){3}\d{1,3}$').search(address))
+
+        for _ in range(999):
+            address = provider.ipv4(network=True)
+            self.assertTrue(len(address) >= 9)
+            self.assertTrue(len(address) <= 18)
+            self.assertTrue(
+                re.compile(r'^(\d{1,3}\.){3}\d{1,3}/\d{1,2}$').search(address))
+
+    def test_ipv6(self):
+        from faker.providers.internet import Provider
+
+        provider = Provider(None)
+
+        for _ in range(999):
+            address = provider.ipv6()
+            self.assertTrue(len(address) >= 3)  # ::1
+            self.assertTrue(len(address) <= 39)
+            self.assertTrue(
+                re.compile(r'^([0-9a-f]{0,4}:){2,7}[0-9a-f]{1,4}$').search(address))
+
+        for _ in range(999):
+            address = provider.ipv6(network=True)
+            self.assertTrue(len(address) >= 4)  # ::/8
+            self.assertTrue(len(address) <= 39 + 4)
+            self.assertTrue(
+                re.compile(r'^([0-9a-f]{0,4}:){2,7}[0-9a-f]{0,4}/\d{1,3}$').search(
+                    address))
+
 
 class GeneratorTestCase(unittest.TestCase):