Skip to content

Commit

Permalink
Parser argument support (joke2k#1243)
Browse files Browse the repository at this point in the history
* Extend Person Provider to support nonbinary suffixes and prefixes (joke2k#1206)

* Implement nonbinary Person Provider

* Address flake8 and isort findings

* Fix import ordering in tests

* Add tests for en Provider to test preexisting suffix scenario

* Refine tests for en Provider

* Close joke2k#1208. Update setup.py `python_requires`

* Add safe domain names (joke2k#1209)

* Add safe_domain_name() to Internet Provider

* Apply DRY to safe domain generation

* Add a test for safe email generation

* Update __init__.py (joke2k#1217)

some grammar fix

* 💂 Fix max_value/positive pyfloat interaction (joke2k#1218)

* 💂 Fix max_value/positive pyfloat interaction

When pyfloat(max_value=100, positive=True) is called, it sometimes
returns negative numbers, which is unexpected.

To fix it, we just need to "cap" our internal minimum value to zero.

This does leave `pyfloat(max_value=100, min_value=-100, positive=True)`
as a call that secretly moves the min_value up to 0 though.

Fixes joke2k#1212.

* 💥 Forbid pyfloat call with incompatible arguments

`pyfloat(positive=True, min_value=-1)` will now throw a ValueError,
since the combination of a request for a positive number, yet a negative
minimum value is probably not what the caller intended.

* Update bban_format for country FI (joke2k#1220)

Fixes joke2k#1219.

* Fix person.ja_JP kana and roman characters (joke2k#1221)

* Add ja_JP kana_names

* Reformat and Add links

* fix PEP8

* JSON and Fixed_Width Structures (joke2k#1223)

* Builder module

* Move to Misc as Methods

* Allow JSON to produce single entry

* json and fixed_width tests

* Improved structure creation and tests

* correcting E231

* Value only lists

* fix E303

* Improved docstrings

* Parser Arguments

* Strip not require

* Better description of error

* Format Parser with Caching

* Correct import position

* Better handing of white space

* Refinement and Tests

* Replace parser with format argument token

* Use format name only if found

* Fix Flake8 E231

* Remove JSON

* Add Argument Group Methods

* Change Add to Set, Correct Tests

* Fix Flake8 C812

Co-authored-by: Cory Donnelly <[email protected]>
Co-authored-by: Flavio Curella <[email protected]>
Co-authored-by: Pavel E. Petrov <[email protected]>
Co-authored-by: coiax <[email protected]>
Co-authored-by: ALMP-SallaH <[email protected]>
Co-authored-by: Yohei Ema <[email protected]>
  • Loading branch information
7 people authored and IlfirinPL committed Apr 9, 2021
1 parent 3487e20 commit 04ac173
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 9 deletions.
85 changes: 78 additions & 7 deletions faker/generator.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import random as random_module
import re

_re_token = re.compile(r'\{\{(\s?)(\w+)(\s?)\}\}')
_re_token = re.compile(r'\{\{\s*(\w+)(:\s*\w+?)?\s*\}\}')
random = random_module.Random()
mod_random = random # compat with name released in 0.8


class Generator:

__config = {}
__config = {
'arguments': {},
}

def __init__(self, **config):
self.providers = []
Expand Down Expand Up @@ -71,7 +73,6 @@ def format(self, formatter, *args, **kwargs):
"""
This is a secure way to make a fake from another Provider.
"""
# TODO: data export?
return self.get_formatter(formatter)(*args, **kwargs)

def get_formatter(self, formatter):
Expand All @@ -95,14 +96,84 @@ def set_formatter(self, name, method):
"""
setattr(self, name, method)

def set_arguments(self, group, argument, value=None):
"""
Creates an argument group, with an individual argument or a dictionary
of arguments. Used with the Generator.parse method.
generator.set_arguments('small', 'max_value', 10)
generator.set_arguments('small', {'min_value': 5, 'max_value': 10})
"""
if group not in self.__config['arguments']:
self.__config['arguments'][group] = {}

if isinstance(argument, dict):
self.__config['arguments'][group] = argument
elif not isinstance(argument, str):
raise ValueError("Arguments must be either a string or dictionary")
else:
self.__config['arguments'][group][argument] = value

def get_arguments(self, group, argument=None):
"""
Get the value of an argument configured within a argument group, or
the entire group as a dictionary.
generator.get_arguments('small', 'max_value')
generator.get_arguments('small')
"""
if group in self.__config['arguments'] and argument:
result = self.__config['arguments'][group].get(argument)
else:
result = self.__config['arguments'].get(group)

return result

def del_arguments(self, group, argument=None):
"""
Delete an argument from an argument group or the entire
argument group.
generator.del_arguments('small')
generator.del_arguments('small', 'max_value')
"""
if group in self.__config['arguments']:
if argument:
result = self.__config['arguments'][group].pop(argument)
else:
result = self.__config['arguments'].pop(group)
else:
result = None

return result

def parse(self, text):
"""
Replaces tokens (like '{{ tokenName }}' or '{{tokenName}}')
with the result from the token method call.
with the result from the token method call. Arguments can be
parsed by using an argument group. '{{ tokenName:group }}'
Example:
generator.set_arguments('red_rgb', {'hue': 'red', 'color_format': 'rgb'})
generator.set_arguments('small', 'max_value', 10)
generator.parse('{{ color:red_rgb }} - {{ pyint:small }}')
"""
return _re_token.sub(self.__format_token, text)

def __format_token(self, matches):
formatter = list(matches.groups())
formatter[1] = str(self.format(formatter[1]))
return ''.join(formatter)
formatter, argument_group = list(matches.groups())
argument_group = argument_group.lstrip(":").strip() if argument_group else ''

if argument_group:
try:
arguments = self.__config['arguments'][argument_group]
except KeyError:
raise AttributeError('Unknown argument group "{}"'.format(argument_group))

formatted = str(self.format(formatter, **arguments))
else:
formatted = str(self.format(formatter))

return ''.join(formatted)
38 changes: 36 additions & 2 deletions tests/test_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def foo_formatter(self):
return 'foobar'

def foo_formatter_with_arguments(self, param='', append=''):
return 'baz' + param + append
return 'baz' + str(param) + str(append)


@pytest.fixture(autouse=True)
Expand Down Expand Up @@ -61,7 +61,41 @@ def test_parse_without_formatter_tokens(self, generator):

def test_parse_with_valid_formatter_tokens(self, generator):
result = generator.parse('This is {{foo_formatter}} a text with "{{ foo_formatter }}"')
assert result == 'This is foobar a text with " foobar "'
assert result == 'This is foobar a text with "foobar"'

def test_arguments_group_with_values(self, generator):
generator.set_arguments('group1', 'argument1', 1)
generator.set_arguments('group1', 'argument2', 2)
assert generator.get_arguments('group1', 'argument1') == 1
assert generator.del_arguments('group1', 'argument2') == 2
assert generator.get_arguments('group1', 'argument2') is None
assert generator.get_arguments('group1') == {'argument1': 1}

def test_arguments_group_with_dictionaries(self, generator):
generator.set_arguments('group2', {'argument1': 3, 'argument2': 4})
assert generator.get_arguments('group2') == {'argument1': 3, 'argument2': 4}
assert generator.del_arguments('group2') == {'argument1': 3, 'argument2': 4}
assert generator.get_arguments('group2') is None

def test_arguments_group_with_invalid_name(self, generator):
assert generator.get_arguments('group3') is None
assert generator.del_arguments('group3') is None

def test_arguments_group_with_invalid_argument_type(self, generator):
with pytest.raises(ValueError) as excinfo:
generator.set_arguments('group', ['foo', 'bar'])
assert str(excinfo.value) == "Arguments must be either a string or dictionary"

def test_parse_with_valid_formatter_arguments(self, generator):
generator.set_arguments('format_name', {"param": "foo", "append": "bar"})
result = generator.parse('This is "{{foo_formatter_with_arguments:format_name}}"')
generator.del_arguments('format_name')
assert result == 'This is "bazfoobar"'

def test_parse_with_unknown_arguments_group(self, generator):
with pytest.raises(AttributeError) as excinfo:
generator.parse('This is "{{foo_formatter_with_arguments:unknown}}"')
assert str(excinfo.value) == 'Unknown argument group "unknown"'

def test_parse_with_unknown_formatter_token(self, generator):
with pytest.raises(AttributeError) as excinfo:
Expand Down

0 comments on commit 04ac173

Please sign in to comment.