Repository: joke2k/faker

Issue:
Would a faker.format() method make sense?
It could enable the syntax:
`print(faker.format('{name} considers a {catch_phrase} in {country}'))`

A sample implementation show how simple it would be:

``` python
def fake_fmt(fmt="{first_name}'s favorite number: {random_int}", fake=None):
    fake = fake or Faker()
    fields = [fld.split('}')[0].split(':')[0] for fld in fmt.split('{')[1:]]
    return fmt.format(**{field: getattr(fake, field)() for field in fields})
```

More test cases at: https://github.com/cclauss/Ten-lines-or-less/blob/master/fake_format.py


Discussion:

Patch:
diff --git a/faker/generator.py b/faker/generator.py
index 40135a1b..b66b89bb 100644
--- a/faker/generator.py
+++ b/faker/generator.py
@@ -4,6 +4,7 @@ from __future__ import unicode_literals
 
 import random as random_module
 import re
+import six
 
 _re_token = re.compile(r'\{\{(\s?)(\w+)(\s?)\}\}')
 random = random_module.Random()
@@ -108,5 +109,5 @@ class Generator(object):
 
     def __format_token(self, matches):
         formatter = list(matches.groups())
-        formatter[1] = self.format(formatter[1])
+        formatter[1] = six.text_type(self.format(formatter[1]))
         return ''.join(formatter)
diff --git a/faker/providers/isbn/__init__.py b/faker/providers/isbn/__init__.py
index ada84898..2d6ace9c 100644
--- a/faker/providers/isbn/__init__.py
+++ b/faker/providers/isbn/__init__.py
@@ -52,7 +52,7 @@ class Provider(BaseProvider):
         :returns: A (registrant, publication) tuple of strings.
         """
         for rule in rules:
-            if rule.min <= reg_pub <= rule.max:
+            if rule.min <= reg_pub[:-1] <= rule.max:
                 reg_len = rule.registrant_length
                 break
         else:
diff --git a/faker/providers/phone_number/fa_IR/__init__.py b/faker/providers/phone_number/fa_IR/__init__.py
index 07f60d0b..ed6a1acb 100644
--- a/faker/providers/phone_number/fa_IR/__init__.py
+++ b/faker/providers/phone_number/fa_IR/__init__.py
@@ -5,14 +5,34 @@ from .. import Provider as PhoneNumberProvider
 class Provider(PhoneNumberProvider):
     formats = (
         # Mobile
+        # Mci
         '+98 91# ### ####',
         '091# ### ####',
+        '+98 990 ### ####',
+        '0990 ### ####',
+        '+98 991 ### ####',
+        '0991 ### ####',
+        # Rightel Mobile prefixes
         '+98 920 ### ####',
         '0920 ### ####',
         '+98 921 ### ####',
         '0921 ### ####',
+        '+98 922 ### ####',
+        '0922 ### ####',
+        # Samantel Mobile prefixes
+        '+98 999 ### ####',
+        '0999 ### ####',
+        # Mtn and Talia
         '+98 93# ### ####',
         '093# ### ####',
+        '+98 901 ### ####',
+        '0901 ### ####',
+        '+98 902 ### ####',
+        '902 ### ####',
+        '+98 903 ### ####',
+        '0903 ### ####',
+        '+98 905 ### ####',
+        '0905 ### ####',
         # Land lines,
         # https://en.wikipedia.org/wiki/List_of_dialling_codes_in_Iran
         '+98 21 #### ####',
diff --git a/faker/providers/python/__init__.py b/faker/providers/python/__init__.py
index eb8a70a6..36317627 100644
--- a/faker/providers/python/__init__.py
+++ b/faker/providers/python/__init__.py
@@ -3,6 +3,7 @@
 from __future__ import unicode_literals
 
 from decimal import Decimal
+import string
 import sys
 
 import six
@@ -32,6 +33,9 @@ class Provider(BaseProvider):
                 ),
             )
 
+    def pystr_format(self, string_format='?#-###{{random_int}}{{random_letter}}', letters=string.ascii_letters):
+        return self.bothify(self.generator.parse(string_format), letters=letters)
+
     def pyfloat(self, left_digits=None, right_digits=None, positive=False,
                 min_value=None, max_value=None):
 


Test patch:
diff --git a/tests/providers/test_isbn.py b/tests/providers/test_isbn.py
index 4d6fab06..fe01c4aa 100644
--- a/tests/providers/test_isbn.py
+++ b/tests/providers/test_isbn.py
@@ -43,8 +43,13 @@ class TestProvider(unittest.TestCase):
     def test_reg_pub_separation(self):
         r1 = RegistrantRule('0000000', '0000001', 1)
         r2 = RegistrantRule('0000002', '0000003', 2)
-        assert self.prov._registrant_publication('0000000', [r1, r2]) == ('0', '000000')
-        assert self.prov._registrant_publication('0000002', [r1, r2]) == ('00', '00002')
+        assert self.prov._registrant_publication('00000000', [r1, r2]) == ('0', '0000000')
+        assert self.prov._registrant_publication('00000010', [r1, r2]) == ('0', '0000010')
+        assert self.prov._registrant_publication('00000019', [r1, r2]) == ('0', '0000019')
+        assert self.prov._registrant_publication('00000020', [r1, r2]) == ('00', '000020')
+        assert self.prov._registrant_publication('00000030', [r1, r2]) == ('00', '000030')
+        assert self.prov._registrant_publication('00000031', [r1, r2]) == ('00', '000031')
+        assert self.prov._registrant_publication('00000039', [r1, r2]) == ('00', '000039')
 
     def test_rule_not_found(self):
         with pytest.raises(Exception):
diff --git a/tests/providers/test_python.py b/tests/providers/test_python.py
index 61f5ca02..ccb93c79 100644
--- a/tests/providers/test_python.py
+++ b/tests/providers/test_python.py
@@ -1,6 +1,10 @@
 #  -*- coding: utf-8 -*-
 
 import unittest
+try:
+    from unittest.mock import patch
+except ImportError:
+    from mock import patch
 
 from faker import Faker
 
@@ -87,3 +91,19 @@ class TestPyfloat(unittest.TestCase):
 
         message = str(raises.exception)
         self.assertEqual(message, expected_message)
+
+
+class TestPystrFormat(unittest.TestCase):
+
+    def setUp(self):
+        self.factory = Faker(includes=['tests.mymodule.en_US'])
+
+    def test_formatter_invocation(self):
+        with patch.object(self.factory, 'foo') as mock_foo:
+            with patch('faker.providers.BaseProvider.bothify',
+                       wraps=self.factory.bothify) as mock_bothify:
+                mock_foo.return_value = 'barbar'
+                value = self.factory.pystr_format('{{foo}}?#?{{foo}}?#?{{foo}}', letters='abcde')
+                assert value.count('barbar') == 3
+                assert mock_foo.call_count == 3
+                mock_bothify.assert_called_once_with('barbar?#?barbar?#?barbar', letters='abcde')