diff --git a/faker/exceptions.py b/faker/exceptions.py index bb744b9a2f..0feb4dd10b 100644 --- a/faker/exceptions.py +++ b/faker/exceptions.py @@ -6,3 +6,7 @@ class UniquenessException(BaseFakerException): """To avoid infinite loops, after a certain number of attempts, the "unique" attribute of the Proxy will throw this exception. """ + + +class UnsupportedFeature(BaseFakerException): + """The requested feature is not available on this system.""" diff --git a/faker/providers/misc/__init__.py b/faker/providers/misc/__init__.py index 35a904072c..5cf7822f0b 100644 --- a/faker/providers/misc/__init__.py +++ b/faker/providers/misc/__init__.py @@ -8,6 +8,8 @@ import uuid import zipfile +from faker.exceptions import UnsupportedFeature + from .. import BaseProvider localized = True @@ -281,6 +283,46 @@ def tar(self, uncompressed_size=65536, num_files=1, min_file_size=4096, compress file_buffer.close() return tar_buffer.getvalue() + def image(self, size=(256, 256), image_format='png', hue=None, luminosity=None): + """Generate an image and draw a random polygon on it using the Python Image Library. + Without it installed, this provider won't be functional. Returns the bytes representing + the image in a given format. + + The argument ``size`` must be a 2-tuple containing (width, height) in pixels. Defaults to 256x256. + + The argument ``image_format`` can be any valid format to the underlying library like ``'tiff'``, + ``'jpeg'``, ``'pdf'`` or ``'png'`` (default). Note that some formats need present system libraries + prior to building the Python Image Library. + Refer to https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html for details. + + The arguments ``hue`` and ``luminosity`` are the same as in the color provider and are simply forwarded to + it to generate both the background and the shape colors. Therefore, you can ask for a "dark blue" image, etc. + + :sample size=2: size=(2, 2), hue='purple', luminosity='bright', image_format='pdf' + :sample size=2: size=(16, 16), hue=[90,270], image_format='ico' + """ + try: + import PIL.Image + import PIL.ImageDraw + except ImportError: + raise UnsupportedFeature("`image` requires the `Pillow` python library.") + + (width, height) = size + image = PIL.Image.new('RGB', size, self.generator.color(hue=hue, luminosity=luminosity)) + draw = PIL.ImageDraw.Draw(image) + draw.polygon( + [ + (self.random_int(0, width), self.random_int(0, height)) + for _ in range(self.random_int(3, 12)) + ], + fill=self.generator.color(hue=hue, luminosity=luminosity), + outline=self.generator.color(hue=hue, luminosity=luminosity), + ) + with io.BytesIO() as fobj: + image.save(fobj, format=image_format) + fobj.seek(0) + return fobj.read() + def dsv(self, dialect='faker-csv', header=None, data_columns=('{{name}}', '{{address}}'), num_rows=10, include_row_ids=False, **fmtparams): diff --git a/tests/providers/test_misc.py b/tests/providers/test_misc.py index d54dfa2b0b..41de338d69 100644 --- a/tests/providers/test_misc.py +++ b/tests/providers/test_misc.py @@ -4,14 +4,20 @@ import json import re import tarfile +import unittest import uuid import zipfile +try: + import PIL.Image +except ImportError: + PIL = None + from unittest.mock import patch import pytest -from faker import Faker +from faker import Faker, exceptions from faker.contrib.pytest.plugin import DEFAULT_LOCALE, DEFAULT_SEED @@ -309,6 +315,20 @@ def test_tar_compression_py3(self, faker): members = tar_handle.getmembers() assert len(members) == num_files + @unittest.skipUnless(PIL, 'requires the Python Image Library') + def test_image(self, faker): + img = PIL.Image.open(io.BytesIO(faker.image())) + assert img.size == (256, 256) + assert img.format == 'PNG' + img = PIL.Image.open(io.BytesIO(faker.image(size=(2, 2), image_format='tiff'))) + assert img.size == (2, 2) + assert img.format == 'TIFF' + + def test_image_no_pillow(self, faker): + with patch.dict("sys.modules", {"PIL": None}): + with pytest.raises(exceptions.UnsupportedFeature): + faker.image() + def test_dsv_with_invalid_values(self, faker): with pytest.raises(ValueError): faker.dsv(num_rows='1') diff --git a/tox.ini b/tox.ini index c45ee1daca..216dca455d 100644 --- a/tox.ini +++ b/tox.ini @@ -11,6 +11,7 @@ deps = ukpostcodeparser>=1.1.1 validators>=0.13.0 sphinx>=2.4,<3.0 + Pillow commands = coverage run --source=faker -m pytest coverage run --source=faker -a -m pytest --exclusive-faker-session tests/pytest/session_overrides