How to create your own converter

There is 3 kinds of converters:

  • the converters that never generate an error, called functions,
  • the filters that only test input value, called tests,
  • the generic form of converters.

Functions

To create a converter that never produces an error, the easiest method is to create a function that accepts the input value as argument and that returns the output value as its only result. Then you just have to wrap this function using the biryan.baseconv.function() converter.

Example of a converter that returns the length of a value:

>>> from biryani import baseconv as conv
...
>>> anything_to_len = conv.function(lambda value: len(value))
...
>>> anything_to_len(u'abc')
(3, None)
>>> anything_to_len([1, 2, 3])
(3, None)
>>> anything_to_len([])
(0, None)

By default, the function converter doesn’t call its wrapped function when input value is None and always returns None, so:

>>> anything_to_len(None)
(None, None)

Most of the times this is the correct behaviour, because in Biryani, when the input value is None a converter is considered to have nothing to convert and should return nothing (aka None).

But, if you want to change this behaviour, you can set the handle_none_value flag:

>>> anything_to_len = conv.function(lambda value: len(value), handle_none_value = True)
...
>>> anything_to_len(None)
Traceback (most recent call last):
TypeError:
Traceback (most recent call last):
TypeError:

In this case, you will have to rewrite your function to handle the None input value:

>>> anything_to_len = conv.function(lambda value: len(value or []), handle_none_value = True)
...
>>> anything_to_len(None)
(0, None)

In the same way, if your function needs to use the state (mainly for internationalization reasons), you need to set the handle_state flag.

For example, here is a function that counts if there is “zero”, “one” or “many” items in input value and returns the localized response.

>>> from biryani import states
...
>>> def zero_one_or_many(value, state = None):
...     if state is None:
...         state = states.default_state
...     size = len(value)
...     if size == 0:
...         return state._(u'zero')
...     elif size == 1:
...         return state._(u'one')
...     else:
...         return state._(u'many')
...
>>> convert_to_zero_one_or_many = conv.function(zero_one_or_many, handle_state = True)
...
>>> convert_to_zero_one_or_many(u'')
(u'zero', None)
>>> convert_to_zero_one_or_many(u'a')
(u'one', None)
>>> convert_to_zero_one_or_many(u'abc')
(u'many', None)
>>>
>>> class FrenchState(object):
...     def _(self, s):
...         return {
...             u'many': u'beaucoup',
...             u'one': u'un',
...             }.get(s, s)
>>> french_state = FrenchState()
...
>>> convert_to_zero_one_or_many(u'', state = french_state)
(u'zero', None)
>>> convert_to_zero_one_or_many(u'a', state = french_state)
(u'un', None)
>>> convert_to_zero_one_or_many(u'abc', state = french_state)
(u'beaucoup', None)

Tests

To create a converter that tests its input value without modifying it and that produces an error when test fails, the easiest method is to create a function that accepts the input value as argument and that returns the result of the test as a boolean. Then you just have to wrap this test function using the biryan.baseconv.test() converter.

Example of a converter that tests whether a password as a sufficient length:

>>> test_valid_password = conv.test(lambda password: len(password) >= 8)
...
>>> test_valid_password(u'abcdefgh')
(u'abcdefgh', None)
>>> test_valid_password(u'123')
(u'123', u'Test failed')

You can changed default error message, using the error argument:

>>> test_valid_password = conv.test(lambda password: len(password) >= 8, error = u'Password too short')
...
>>> test_valid_password(u'123')
(u'123', u'Password too short')

By default, the test converter doesn’t call its wrapped function when input value is None and always returns None, so:

>>> test_valid_password(None)
(None, None)

Most of the times this is the correct behaviour, because in Biryani, when the input value is None a test is considered to have nothing to test and should return nothing (aka None).

But, if you want to change this behaviour, you can set the handle_none_value flag:

>>> test_valid_password = conv.test(lambda password: len(password) >= 8, handle_none_value = True)
...
>>> test_valid_password(None)
Traceback (most recent call last):
TypeError:
Traceback (most recent call last):
TypeError:

In this case, you will have to rewrite your test to handle the None input value:

>>> test_valid_password = conv.test(lambda password: len(password or u'') >= 8, handle_none_value = True)
...
>>> test_valid_password(None)
(None, u'Test failed')

In the same way, if your test needs to use the state (mainly for internationalization reasons), you need to set the handle_state flag.

For example, here is a filter that tests whether the localized version of a string as an even length:

>>> def has_even_len(value, state = None):
...     if state is None:
...         state = states.default_state
...     return len(state._(value)) % 2 == 0
...
>>> test_has_even_len = conv.test(has_even_len, handle_state = True)
...
>>> test_has_even_len(u'many')
(u'many', None)
>>> test_has_even_len(u'one')
(u'one', u'Test failed')
>>> test_has_even_len(u'one', state = french_state)
(u'one', None)
>>> test_has_even_len(u'two', state = french_state)
(u'two', u'Test failed')

Generic converters

Example of a custom converter that accepts a couple of passwords as input value, compares the two passwords and either generates an error when they differ or are two short, or returns the valid password when they match.

A converter is a function that has two parameters, the input value and the state, and that returns a couple (output value, eventual error message).

>>> def validate_password(passwords, state = None):
...     if state is None:
...         state = states.default_state
...     # Generally, a converter should ignore a ``None`` input value:
...     if passwords is None:
...         return passwords, None
...     # Test passwords.
...     if len(passwords) < 2:
...         # When an error occurs and output value can not be computed, return input value with the error message.
...         # Every error message is localized using ``state._()``.
...         return passwords, state._(u'Missing passwords')
...     password = passwords[0]
...     if password != passwords[1]:
...         return passwords, state._(u'Password mismatch')
...     if len(password) < 8:
...         return password, state._(u'Password too short')
...     return password, None
>>> validate_password([u'abcdefgh', u'abcdefgh'])
(u'abcdefgh', None)
>>> validate_password([u'abc', u'abc'])
(u'abc', u'Password too short')
>>> validate_password([u'abcdefgh'])
([u'abcdefgh'], u'Missing passwords')

To create a customizable converter you should write a function accepting customizing options as parameters and returning a customized converters.

For example, to transform our password validator to add a minimal password length:

>>> def validate_password(min_len = 6):
...     def validate_password_converter(passwords, state = None):
...         if state is None:
...             state = states.default_state
...         # Generally, a converter should ignore a ``None`` input value:
...         if passwords is None:
...             return passwords, None
...         # Test passwords.
...         if len(passwords) < 2:
...             # When an error occurs and output value can not be computed, return input value with the error message.
...             # Every error message is localized using ``state._()``.
...             return passwords, state._(u'Missing passwords')
...         password = passwords[0]
...         if password != passwords[1]:
...             return passwords, state._(u'Password mismatch')
...         if len(password) < min_len:
...             return password, state._(u'Password too short')
...         return password, None
...     return validate_password_converter
>>> validate_password()([u'abcdefgh', u'abcdefgh'])
(u'abcdefgh', None)
>>> validate_password()([u'abc', u'abc'])
(u'abc', u'Password too short')
>>> validate_password(3)([u'abc', u'abc'])
(u'abc', None)

Note

This converter could also be written by combining existing converters:

>>> def validate_password(min_len = 6):
...     return conv.pipe(
...         conv.test(lambda passwords: len(passwords) >= 2,
...             error = u'Missing passwords'),
...         conv.test(lambda passwords: passwords[0] == passwords[1],
...             error = u'Password mismatch'),
...         conv.function(lambda passwords: passwords[0]),
...         conv.test(lambda password: len(password) >= min_len,
...             error = u'Password too short'),
...         )
>>> validate_password()([u'abcdefgh', u'abcdefgh'])
(u'abcdefgh', None)
>>> validate_password()([u'abc', u'abc'])
(u'abc', u'Password too short')
>>> validate_password(3)([u'abc', u'abc'])
(u'abc', None)

Table Of Contents

Previous topic

Tutorial 2

Next topic

Internationalization & Localization

This Page