source: main/waeup.sirp/branches/ulif-experimental-folders/src/waeup/sirp/utils/converters.txt @ 8794

Last change on this file since 8794 was 5140, checked in by uli, 15 years ago

Update all unit tests that use the ZCA to run inside the new unit test layer.

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