Tutorial 2ΒΆ

>>> from biryani import baseconv as conv
>>> input_value = u'42'
>>> output_value, error = conv.input_to_float(input_value)
>>> output_value, error
(42.0, None)
>>> conv.input_to_float('forty two')
('forty two', u'Value must be a float')

Converters usually don’t test their input value:

>>> conv.input_to_float(42)
Traceback (most recent call last):
AttributeError:
Traceback (most recent call last):
AttributeError:

So, to ensure that input value is an unicode string, we need to chain several converters:

>>> conv.pipe(
...     conv.test_isinstance(unicode),
...     conv.input_to_float,
...     )(u'42')
(42.0, None)
>>> conv.pipe(
...     conv.test_isinstance(unicode),
...     conv.input_to_float,
...     )(42)
(42, u"Value is not an instance of <type 'unicode'>")
>>> conv.pipe(
...     conv.test_isinstance(unicode),
...     conv.input_to_float,
...     )('42')
('42', u"Value is not an instance of <type 'unicode'>")

Use conv.check to extract value from conversion result or raise an exception when an error occurs:

>>> conv.check(conv.pipe(
...     conv.test_isinstance(unicode),
...     conv.input_to_float,
...     ))(u'42')
42.0
>>> conv.check(conv.pipe(
...     conv.test_isinstance(unicode),
...     conv.input_to_float,
...     ))(42)
Traceback (most recent call last):
ValueError:
Traceback (most recent call last):
ValueError:

Add a custom function to convert string to unicode when needed, and store resulting converter into a variable:

>>> any_string_to_float = conv.pipe(
...     conv.function(lambda value: value.decode('utf-8') if isinstance(value, str) else value),
...     conv.test_isinstance(unicode),
...     conv.input_to_float,
...     )
...
>>> any_string_to_float('42')
(42.0, None)
>>> any_string_to_float(u'42')
(42.0, None)

Note

The builtin converter biryani.baseconv.decode_str(): does the same thing as the conversion function above.

So the converter could be simplified to:

>>> anything_to_float = conv.pipe(
...     conv.decode_str(),
...     conv.test_isinstance(unicode),
...     conv.input_to_float,
...     )
...
>>> any_string_to_float('42')
(42.0, None)
>>> any_string_to_float(u'42')
(42.0, None)

We can harden the custom function to convert anything to unicode:

>>> anything_to_float = conv.pipe(
...     conv.function(lambda value: value.decode('utf-8') if isinstance(value, str) else unicode(value)),
...     conv.test_isinstance(unicode),
...     conv.input_to_float,
...     )
...
>>> anything_to_float(42)
(42.0, None)

Add conv.cleanup_line to strip spaces from string and convert it to None when empty:

>>> anything_to_float = conv.pipe(
...     conv.function(lambda value: value.decode('utf-8') if isinstance(value, str) else unicode(value)),
...     conv.test_isinstance(unicode),
...     conv.cleanup_line,
...     conv.input_to_float,
...     )
...
>>> anything_to_float('  42   ')
(42.0, None)
>>> anything_to_float(u'     ')
(None, None)

Add conv.not_none to generate an error when value is None:

>>> anything_to_float = conv.pipe(
...     conv.function(lambda value: value.decode('utf-8') if isinstance(value, str) else unicode(value)),
...     conv.test_isinstance(unicode),
...     conv.cleanup_line,
...     conv.input_to_float,
...     conv.not_none,
...     )
...
>>> anything_to_float(u'     ')
(None, u'Missing value')

Use a custom test to ensure that float is a valid latitude:

>>> anything_to_latitude = conv.pipe(
...     conv.function(lambda value: value.decode('utf-8') if isinstance(value, str) else unicode(value)),
...     conv.test_isinstance(unicode),
...     conv.cleanup_line,
...     conv.input_to_float,
...     conv.test(lambda value: -180 <= value <= 180),
...     conv.not_none,
...     )
...
>>> anything_to_latitude('50')
(50.0, None)
>>> anything_to_latitude('')
(None, u'Missing value')
>>> anything_to_latitude(' -123.4 ')
(-123.4, None)
>>> anything_to_latitude(u'500')
(500.0, u'Test failed')

Add an explicit error message when latitude is not between -180 and 180 degrees:

>>> anything_to_latitude = conv.pipe(
...     conv.function(lambda value: value.decode('utf-8') if isinstance(value, str) else unicode(value)),
...     conv.test_isinstance(unicode),
...     conv.cleanup_line,
...     conv.input_to_float,
...     conv.test(lambda value: -180 <= value <= 180, error = U'Latitude must be between -180 and 180'),
...     conv.not_none,
...     )
...
>>> anything_to_latitude(u'500')
(500.0, u'Latitude must be between -180 and 180')

Generalize the converter to a function that accepts any bound:

>>> def anything_to_bounded_float(min_bound, max_bound):
...     return conv.pipe(
...         conv.function(lambda value: value.decode('utf-8') if isinstance(value, str) else unicode(value)),
...         conv.test_isinstance(unicode),
...         conv.cleanup_line,
...         conv.input_to_float,
...         conv.test(lambda value: min_bound <= value <= max_bound,
...             error = 'Value must be between {0} and {1}'.format(min_bound, max_bound)),
...         conv.not_none,
...         )
...
>>> anything_to_bounded_float(-180, 180)(90)
(90.0, None)

Note

The builtin converter biryani.baseconv.test_between(): does the same thing as the test on bounds above.

So the converter could be simplified to:

>>> def anything_to_bounded_float(min_bound, max_bound):
...     return conv.pipe(
...         conv.function(lambda value: value.decode('utf-8') if isinstance(value, str) else unicode(value)),
...         conv.test_isinstance(unicode),
...         conv.cleanup_line,
...         conv.input_to_float,
...         conv.test_between(min_bound, max_bound),
...         conv.not_none,
...         )
...
>>> anything_to_bounded_float(-180, 180)(90)
(90.0, None)

Use the generalized function to convert a dictionary containing both a latitude and a longitude:

>>> dict_to_lat_long = conv.struct(dict(
...     latitude = anything_to_bounded_float(-180, 180),
...     longitude = anything_to_bounded_float(-360, 360),
...     ))
...
>>> dict_to_lat_long(dict(latitude = '-12.34', longitude = u"45"))
({'latitude': -12.34, 'longitude': 45.0}, None)
>>> dict_to_lat_long(dict(latitude = '-12.34', longitude = u"45,6"))
({'latitude': -12.34, 'longitude': u'45,6'}, {'longitude': u'Value must be a float'})
>>> dict_to_lat_long(dict(latitude = None, longitude = ''))
({'latitude': None, 'longitude': None}, {'latitude': u'Missing value', 'longitude': u'Missing value'})
>>> dict_to_lat_long(None)
(None, None)

Converters working on complex structures can be chained too:

>>> dict_to_lat_long = conv.pipe(
...     conv.test_isinstance(dict),
...     conv.struct(dict(
...         latitude = anything_to_bounded_float(-180, 180),
...         longitude = anything_to_bounded_float(-360, 360),
...         )),
...     conv.not_none,
...     )
...
>>> dict_to_lat_long(dict(latitude = '-12.34', longitude = u"45"))
({'latitude': -12.34, 'longitude': 45.0}, None)
>>> dict_to_lat_long(dict(latitude = '-12.34', longitude = u"45,6"))
({'latitude': -12.34, 'longitude': u'45,6'}, {'longitude': u'Value must be a float'})
>>> dict_to_lat_long(['-12.34', u"45"])
(['-12.34', u'45'], u"Value is not an instance of <type 'dict'>")
>>> dict_to_lat_long(None)
(None, u'Missing value')

Previous topic

Tutorial 1: Validating a web form

Next topic

How to create your own converter

This Page