source: main/waeup.sirp/trunk/src/waeup/sirp/utils/converters.txt @ 4979

Last change on this file since 4979 was 4920, checked in by uli, 15 years ago

Make unit tests run again with the new package layout.

File size: 19.9 KB
Line 
1:mod:`waeup.sirp.utils.converters` -- Converters
2************************************************
3
4.. module:: waeup.sirp.utils.converters
5
6Converters for :mod:`zope.schema` based data.
7
8.. contents::
9
10
11:Test-Layer: unit
12
13As converters are registered as adapters, we have to grok the package
14first:
15
16    >>> import grok
17    >>> grok.testing.grok('waeup')
18
19Constants
20=========
21
22.. attribute:: NONE_STRING_VALUE
23
24   This value determines, which string represents
25   'non-values'. Current setting is:
26
27      >>> from waeup.sirp.utils.converters import NONE_STRING_VALUE
28      >>> NONE_STRING_VALUE
29      ''
30
31   If this value is found during conversion from strings, the value to
32   set will become ``None``.
33
34
35Adapters
36========
37
38The :mod:`waeup.sirp.utils.converters` module is basically a collection of
39adapters for field types of :mod:`zope.schema`.
40
41:class:`Converter`
42------------------
43
44.. class:: Converter(field)
45
46   Base class for converter classes. You cannot create working
47   instances of this class. Instead derive from it and implement the
48   missing classes.
49
50   `field` should be an object implementing some interface from
51   :mod:`zope.schema` like :class:`zope.schema.TextLine`,
52   `zope.schema.Choice` or similar.
53
54   .. method:: provides(waeup.sirp.interfaces.ISchemaTypeConverter)
55
56   .. method:: _convertValueFromString(string)
57
58       Raises :exc:`NotImplementedError`.
59
60       Called by :meth:`fromString()` to convert a given string to a
61       value appropriate for the applied field type. The string might
62       be ``None`` or ``missing_value`` from field definition.
63
64       Override this method in derived classes to get a working
65       converter.
66
67   .. method:: _convertValueToString(value)
68
69       Raises :exc:`NotImplementedError`.
70
71       Called by :meth:`toString()` to convert a given value to a
72       string suitable to be stored for instance in CSV files.
73
74       Override this method in derived classes to get a working
75       converter.
76
77   .. method:: fromString(string[, strict=True])
78
79       Compute a value for the context field from the given
80       `string`. Be aware that this might fail for several reasons,
81       for instance because the string cannot be converted to an
82       integer (if the applied field is an Integer field), etc.
83
84       Use `strict` to enable/disable validations of transformed
85       data.
86
87   .. method:: toString(value[, strict=True])
88
89       Generate a string from ``value``, suitable to be stored in CSV
90       files or similar. This method does preliminary checks, handles
91       defaults and ``missing_value`` stuff and then calls
92       :meth:`_convertValueToString(value)` to do the actual
93       conversion.
94
95       Use `strict` to enable/disable validations of transformed
96       data.
97       
98
99
100:class:`TextConverter`
101----------------------
102
103.. class:: TextConverter(field)
104
105   Create a converter for text fields.
106
107   `field` must be an object implementing
108   ``zope.schema.interfaces.IText``. This is true for instance for
109   :class:`zope.schema.TextLine` fields and most other, purely
110   text-related field types.
111
112   Instances of this class are available as adapters from :mod:`IText`
113   to :mod:`ISchemaTypeConverter`.
114
115   .. method:: adapts(zope.schema.IText)
116
117   .. method:: provides(waeup.sirp.interfaces.ISchemaTypeConverter)
118
119   .. method:: fromString(string[, strict=True])
120
121       Compute a unicode string value from the given `string`. Be
122       aware that this might fail if no conversion of the delivered
123       data is possible.
124
125       Use `strict` to enable/disable validations of transformed
126       data.
127
128   .. method:: toString(value[, strict=True])
129
130       Return `value` as string or ``None``.
131
132       If `value` is ``None`` (or field's `missing_value`), ``None``
133       is returned.
134
135       If the value is not valid for the applied field an exception
136       might be raised.
137
138       Use `strict` to enable/disable validations of input data.
139
140
141:class:`BoolConverter`
142----------------------
143
144.. class:: BoolConverter(field)
145
146   Create a converter for boolean fields.
147
148   `field` must be an object implementing
149   ``zope.schema.interfaces.IBool``.
150
151   Instances of this class are available as adapters from :mod:`IBool`
152   to :mod:`ISchemaTypeConverter`.
153
154   .. method:: adapts(zope.schema.IBool)
155
156   .. method:: provides(waeup.sirp.interfaces.ISchemaTypeConverter)
157
158   .. method:: fromString(string[, strict=True])
159
160       Compute an integer number value from the given `string`. In
161       general '1', 'true', and 'yes' are interpreted as ``True``,
162       ``None`` is interpreted as 'no value' and all other values are
163       interpreted as ``True``.
164
165       Use `strict` to enable/disable validations of transformed
166       data.
167
168   .. method:: toString(value[, strict=True])
169
170       Return `value` as string or ``None``.
171
172       If `value` is ``None`` (or field's `missing_value`), ``None``
173       is returned. Otherwise ``0`` or ``1`` are returned.
174
175       If the value is not valid for the applied field (i.e. not an
176       integer or `missing_value`) an exception
177       might be raised.
178
179       Use `strict` to enable/disable validations of input data.
180
181
182:class:`IntConverter`
183---------------------
184
185.. class:: IntConverter(field)
186
187   Create a converter for integer fields.
188
189   `field` must be an object implementing
190   ``zope.schema.interfaces.IInt``.
191
192   Instances of this class are available as adapters from :mod:`IInt`
193   to :mod:`ISchemaTypeConverter`.
194
195   .. method:: adapts(zope.schema.IInt)
196
197   .. method:: provides(waeup.sirp.interfaces.ISchemaTypeConverter)
198
199   .. method:: fromString(string[, strict=True])
200
201       Compute an integer number value from the given `string`. Be
202       aware that this might fail if no conversion of the delivered
203       data is possible.
204
205       Use `strict` to enable/disable validations of transformed
206       data.
207
208   .. method:: toString(value[, strict=True])
209
210       Return `value` as string or ``None``.
211
212       If `value` is ``None`` (or field's `missing_value`), ``None``
213       is returned.
214
215       If the value is not valid for the applied field (i.e. not an
216       integer or `missing_value`) an exception
217       might be raised.
218
219       Use `strict` to enable/disable validations of input data.
220
221
222
223:class:`ChoiceConverter`
224------------------------
225
226.. class:: ChoiceConverter(field)
227
228   Create a converter for choice fields.
229
230   `field` must be an object implementing
231   ``zope.schema.interfaces.IChoice``.
232
233   Instances of this class are available as adapters from :mod:`IChoice`
234   to :mod:`ISchemaTypeConverter`.
235
236   .. method:: adapts(zope.schema.IChoice)
237
238   .. method:: provides(waeup.sirp.interfaces.ISchemaTypeConverter)
239
240   .. method:: fromString(string[, strict=False])
241
242       Compute a value from the given `string`. Be aware that this
243       might fail if no conversion of the delivered data is possible.
244
245       As choices generally require a certain set of allowed values,
246       only these values be allowed, in their tokenized form. The
247       converter takes the given string as token and then tries to
248       generate the appropriate value out of it (see examples below).
249
250       Use `strict` to enable/disable validations of transformed
251       data.
252
253       As an invalid value will result in a `KeyError`, additional
254       validation checks are disabled with this converter. This speeds
255       up things as expensive lookups are avoided.
256
257   .. method:: toString(value[, strict=False])
258
259       Return `value` as string or ``None``.
260
261       If `value` is ``None`` (or field's `missing_value`), ``None``
262       is returned.
263
264       If the value is not valid for the applied field (i.e. not an
265       integer or `missing_value`) an exception
266       might be raised.
267
268       Use `strict` to enable/disable validations of input data.
269
270       As an invalid value will result in a `ValueError`, additional
271       validation checks are disabled with this converter. This speeds
272       up things as expensive lookups are avoided.
273
274
275Examples
276========
277
278Text field converters
279---------------------
280
281We create a field that also implements ``IText``, a regular
282``TextLine`` field:
283
284    >>> from zope.schema import TextLine
285    >>> field = TextLine(title=u'Some Title',
286    ...                  default=u'Default Value',
287    ...                  required=True)
288
289Now we can get a converter for this field by explicitly creating a
290:class:`TextConverter` instance:
291
292    >>> from waeup.sirp.utils.converters import TextConverter
293    >>> converter = TextConverter(field)
294
295Or we can just grab a registered adapter:
296
297    >>> from waeup.sirp.interfaces import ISchemaTypeConverter
298    >>> converter = ISchemaTypeConverter(field)
299
300This will select the correct converter for us automatically.
301
302    >>> converter
303    <waeup.sirp.utils.converters.TextConverter object at 0x...>
304
305Now we can convert strings to this type:
306
307    >>> converter.fromString('blah')
308    u'blah'
309
310And back:
311
312    >>> converter.toString(u'blah')
313    'blah'
314
315Okay, not very surprising. But the field definitions can help also
316deliver values, if the given value is missing:
317
318    >>> converter.fromString(None)
319    u'Default Value'
320
321``None`` is not an acceptable value for fields which are required but
322provide no default:
323
324    >>> field = TextLine(title=u'Some Title',
325    ...                  required=True)
326
327    >>> converter = ISchemaTypeConverter(field)
328    >>> converter.fromString(None)
329    Traceback (most recent call last):
330    ...
331    RequiredMissing
332
333If we want to avoid this type of exception (and risk non-applicable
334data to be stored), we can use the ``strict`` parameter of
335``fromString()``:
336
337    >>> converter.fromString(None, strict=False) is None
338    True
339
340
341If a field is not required, we will get the ``missing_value`` type in
342same case:
343
344    >>> field = TextLine(title=u'Some Title',
345    ...                  required=False)
346
347    >>> converter = ISchemaTypeConverter(field)
348    >>> converter.fromString(None) is None
349    True
350
351We can also set a value that indicates missing data (``None`` by default):
352
353    >>> field = TextLine(title=u'Some Title',
354    ...                  required=False,
355    ...                  missing_value=u'<NO-VALUE>')
356
357    >>> converter = ISchemaTypeConverter(field)
358    >>> converter.fromString(None)
359    u'<NO-VALUE>'
360
361And put it back:
362
363    >>> converter.toString(u'<NO-VALUE>') is None
364    True
365
366Bool field converters
367---------------------
368
369We create a field that also implements ``IBool``, a regular
370``Bool`` field:
371
372    >>> from zope.schema import Bool
373    >>> field = Bool(title=u'Some truth',
374    ...             default=True,
375    ...             required=True)
376
377Now we can get a converter for this field by explicitly creating a
378:class:`BoolConverter` instance:
379
380    >>> from waeup.sirp.utils.converters import BoolConverter
381    >>> converter = BoolConverter(field)
382
383Or we can just grab a registered adapter:
384
385    >>> from waeup.sirp.interfaces import ISchemaTypeConverter
386    >>> converter = ISchemaTypeConverter(field)
387
388This will select the correct converter for us automatically.
389
390    >>> converter
391    <waeup.sirp.utils.converters.BoolConverter object at 0x...>
392
393Now we can convert strings to this type:
394
395    >>> converter.fromString('yes')
396    True
397
398    >>> converter.fromString('Yes')
399    True
400
401    >>> converter.fromString('yEs')
402    True
403
404    >>> converter.fromString('1')
405    True
406
407    >>> converter.fromString('True')
408    True
409
410    >>> converter.fromString('0')
411    False
412
413    >>> converter.fromString('no')
414    False
415
416    >>> converter.fromString('false')
417    False
418
419
420And back:
421
422    >>> converter.toString(True)
423    '1'
424
425    >>> converter.toString(False)
426    '0'
427
428Okay, not very surprising. But the field definitions can help also
429deliver values, if the given value is missing:
430
431    >>> converter.fromString(None)
432    True
433
434``None`` is not an acceptable value for fields which are required but
435provide no default:
436
437    >>> field = Bool(title=u'Some Truth',
438    ...             required=True)
439
440    >>> converter = ISchemaTypeConverter(field)
441    >>> converter.fromString(None)
442    Traceback (most recent call last):
443    ...
444    RequiredMissing
445
446If we want to avoid this type of exception (and risk non-applicable
447data to be stored), we can use the ``strict`` parameter of
448``fromString()``:
449
450    >>> converter.fromString(None, strict=False) is None
451    True
452
453The same for the inverse operation:
454
455    >>> converter.toString(None)
456    Traceback (most recent call last):
457    ...
458    RequiredMissing
459
460    >>> converter.toString(None, strict=False) is None
461    True
462
463
464If a field is not required, we will get the ``missing_value`` type in
465same case:
466
467    >>> field = Bool(title=u'Some Title',
468    ...             required=False)
469
470    >>> converter = ISchemaTypeConverter(field)
471    >>> converter.fromString(None) is None
472    True
473
474    >>> converter.toString(None) is None
475    True
476
477
478Int field converters
479--------------------
480
481We create a field that also implements ``IInt``, a regular
482``Int`` field:
483
484    >>> from zope.schema import Int
485    >>> field = Int(title=u'Some number',
486    ...             default=12,
487    ...             required=True)
488
489Now we can get a converter for this field by explicitly creating a
490:class:`IntConverter` instance:
491
492    >>> from waeup.sirp.utils.converters import IntConverter
493    >>> converter = IntConverter(field)
494
495Or we can just grab a registered adapter:
496
497    >>> from waeup.sirp.interfaces import ISchemaTypeConverter
498    >>> converter = ISchemaTypeConverter(field)
499
500This will select the correct converter for us automatically.
501
502    >>> converter
503    <waeup.sirp.utils.converters.IntConverter object at 0x...>
504
505Now we can convert strings to this type:
506
507    >>> converter.fromString('666')
508    666
509
510And back:
511
512    >>> converter.toString(666)
513    '666'
514
515Okay, not very surprising. But the field definitions can help also
516deliver values, if the given value is missing:
517
518    >>> converter.fromString(None)
519    12
520
521``None`` is not an acceptable value for fields which are required but
522provide no default:
523
524    >>> field = Int(title=u'Some Title',
525    ...             required=True)
526
527    >>> converter = ISchemaTypeConverter(field)
528    >>> converter.fromString(None)
529    Traceback (most recent call last):
530    ...
531    RequiredMissing
532
533If we want to avoid this type of exception (and risk non-applicable
534data to be stored), we can use the ``strict`` parameter of
535``fromString()``:
536
537    >>> converter.fromString(None, strict=False) is None
538    True
539
540The same for the inverse operation:
541
542    >>> converter.toString(None)
543    Traceback (most recent call last):
544    ...
545    RequiredMissing
546
547    >>> converter.toString(None, strict=False) is None
548    True
549
550
551If a field is not required, we will get the ``missing_value`` type in
552same case:
553
554    >>> field = Int(title=u'Some Title',
555    ...             required=False)
556
557    >>> converter = ISchemaTypeConverter(field)
558    >>> converter.fromString(None) is None
559    True
560
561    >>> converter.toString(None) is None
562    True
563
564
565Choice field converters
566-----------------------
567
568Before choices really work in unit tests, we have to make sure, that
569the following adapters are registered (this is normally done during
570startup automatically):
571
572    >>> from zc.sourcefactory.browser.source import FactoredTerms
573    >>> from zc.sourcefactory.browser.token import (
574    ...   fromString, fromUnicode, fromInteger, fromPersistent)
575    >>> from zope.component import getGlobalSiteManager
576    >>> gsm = getGlobalSiteManager()
577    >>> gsm.registerAdapter(FactoredTerms)
578    >>> gsm.registerAdapter(fromString)
579    >>> gsm.registerAdapter(fromUnicode)
580    >>> gsm.registerAdapter(fromInteger)
581    >>> gsm.registerAdapter(fromPersistent)
582
583We create a field that also implements ``IChoice``, a regular
584``Choice`` field:
585
586    >>> from zope.schema import Choice
587    >>> field = Choice(title=u'Some number',
588    ...                default=u'a',
589    ...                values=['a', 'b', 'c'],
590    ...                required=True)
591
592Now we can get a converter for this field by explicitly creating a
593:class:`ChoiceConverter` instance:
594
595    >>> from waeup.sirp.utils.converters import ChoiceConverter
596    >>> converter = ChoiceConverter(field)
597
598Or we can just grab a registered adapter:
599
600    >>> from waeup.sirp.interfaces import ISchemaTypeConverter
601    >>> converter = ISchemaTypeConverter(field)
602
603This will select the correct converter for us automatically.
604
605    >>> converter
606    <waeup.sirp.utils.converters.ChoiceConverter object at 0x...>
607
608Now we can convert strings to this type:
609
610    >>> converter.fromString('a')
611    'a'
612
613or values back to strings:
614
615    >>> converter.toString('a')
616    'a'
617
618Non-listed values will not be accepted:
619
620    >>> converter.fromString('nonsense')
621    Traceback (most recent call last):
622    ...
623    LookupError: nonsense
624
625Also non-string values will work:
626
627    >>> from zope.schema import Choice
628    >>> field = Choice(title=u'Some number',
629    ...                default=2,
630    ...                values=[1, 2, 3],
631    ...                required=True)
632
633    >>> converter = ISchemaTypeConverter(field)
634    >>> converter.fromString('1')
635    1
636
637If we define an own source, then it will cause no problems:
638
639    >>> from zc.sourcefactory.basic import BasicSourceFactory
640    >>> class CustomSource(BasicSourceFactory):
641    ...   def getValues(self):
642    ...     return [1,2,3]
643    ...   
644    ...   def getTitle(self, value):
645    ...     return "Number %s" % (value,)
646
647    >>> field = Choice(title=u'Some number',
648    ...                default=2,
649    ...                source=CustomSource(),
650    ...                required=True)
651
652    >>> converter = ISchemaTypeConverter(field)
653    >>> converter.fromString('1')
654    1
655
656Note, that when asking for conversion of Choice fields, you must
657deliver the token value of the source/vocabulary. This might be the
658same as the actual value as string, but it it might be different,
659depending of the kind of source/vocabulary used.
660
661Furthermore, when writing 'exporters', i.e. components that turn a
662value into a string for usage in CSV files, then you have to save the
663token value of a term.
664
665Using the ;meth:`toString()` will deliver the token for us:
666
667    >>> converter.toString(1)
668    '1'
669
670    >>> from waeup.sirp.interfaces import ICourse
671    >>> from zope.schema import getFields
672    >>> field = getFields(ICourse)['semester']
673
674    >>> converter = ISchemaTypeConverter(field)
675    >>> converter.fromString('0')
676    0
677
678    >>> converter.toString(1)
679    '1'
680
681A more complex (but still realistic example) for conversion of
682:mode:`zope.schema.Choice` fields follows. Here we have a special kind
683of object, the `Cave` class, and get their `name` attribute as token.
684
685    >>> class Cave(object):
686    ...   name = 'Home sweet home'
687
688    >>> def tokenizer(cave):
689    ...   return cave.name
690
691The tokenizer has to be registered as an Cave-to-IToken adapter to
692keep :mod:`zc.sourcefactory` happy:
693
694    >>> from zc.sourcefactory.interfaces import IToken
695    >>> from zope.component import getGlobalSiteManager
696    >>> gsm = getGlobalSiteManager()
697    >>> gsm.registerAdapter(tokenizer, required=(Cave,), provided=IToken)
698
699Now we finally can create two objects of `Cave` and make a field, that
700gives the choice between these two objects:
701
702    >>> obj1 = Cave()
703    >>> obj1.name = 'Wilma'
704
705    >>> obj2 = Cave()
706    >>> obj2.name = 'Fred'
707
708    >>> from zc.sourcefactory.basic import BasicSourceFactory
709    >>> class CustomSource(BasicSourceFactory):
710    ...   def getValues(self):
711    ...     return [obj1, obj2]
712    ...   
713    ...   def getTitle(self, value):
714    ...     return value.name
715
716    >>> field = Choice(title=u'Some cave',
717    ...                default=obj1,
718    ...                source=CustomSource(),
719    ...                required=True)
720
721We get a converter for this field in the usual way. The
722:meth:`waeup.sirp.utils.converters.fromString()` method will return one of
723the objects if we feed it with ``'Wilma'`` or ``'Fred'``:
724
725    >>> converter = ISchemaTypeConverter(field)
726    >>> result = converter.fromString('Wilma')
727    >>> result
728    <Cave object at 0x...>
729
730    >>> result is obj1, result.name
731    (True, 'Wilma')
732
733If we use the converter the other way round, it will call the
734tokenizer above and deliver ``'Wilma'`` or ``'Fred'`` as they are the
735tokens of the objects in question:
736
737    >>> converter.toString(obj2)
738    'Fred'
Note: See TracBrowser for help on using the repository browser.