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

Last change on this file since 5690 was 5328, checked in by uli, 14 years ago

Merge changes from ulif-fasttables back into trunk.

File size: 23.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
271:class:`DateConverter`
272----------------------
273
274.. autoclass:: waeup.sirp.utils.converters.DateConverter
275   :members:
276   :inherited-members:
277
278
279Examples
280========
281
282Text field converters
283---------------------
284
285We create a field that also implements ``IText``, a regular
286``TextLine`` field:
287
288    >>> from zope.schema import TextLine
289    >>> field = TextLine(title=u'Some Title',
290    ...                  default=u'Default Value',
291    ...                  required=True)
292
293Now we can get a converter for this field by explicitly creating a
294:class:`TextConverter` instance:
295
296    >>> from waeup.sirp.utils.converters import TextConverter
297    >>> converter = TextConverter(field)
298
299Or we can just grab a registered adapter:
300
301    >>> from waeup.sirp.interfaces import ISchemaTypeConverter
302    >>> converter = ISchemaTypeConverter(field)
303
304This will select the correct converter for us automatically.
305
306    >>> converter
307    <waeup.sirp.utils.converters.TextConverter object at 0x...>
308
309Now we can convert strings to this type:
310
311    >>> converter.fromString('blah')
312    u'blah'
313
314And back:
315
316    >>> converter.toString(u'blah')
317    'blah'
318
319Okay, not very surprising. But the field definitions can help also
320deliver values, if the given value is missing:
321
322    >>> converter.fromString(None)
323    u'Default Value'
324
325``None`` is not an acceptable value for fields which are required but
326provide no default:
327
328    >>> field = TextLine(title=u'Some Title',
329    ...                  required=True)
330
331    >>> converter = ISchemaTypeConverter(field)
332    >>> converter.fromString(None)
333    Traceback (most recent call last):
334    ...
335    RequiredMissing
336
337If we want to avoid this type of exception (and risk non-applicable
338data to be stored), we can use the ``strict`` parameter of
339``fromString()``:
340
341    >>> converter.fromString(None, strict=False) is None
342    True
343
344
345If a field is not required, we will get the ``missing_value`` type in
346same case:
347
348    >>> field = TextLine(title=u'Some Title',
349    ...                  required=False)
350
351    >>> converter = ISchemaTypeConverter(field)
352    >>> converter.fromString(None) is None
353    True
354
355We can also set a value that indicates missing data (``None`` by default):
356
357    >>> field = TextLine(title=u'Some Title',
358    ...                  required=False,
359    ...                  missing_value=u'<NO-VALUE>')
360
361    >>> converter = ISchemaTypeConverter(field)
362    >>> converter.fromString(None)
363    u'<NO-VALUE>'
364
365And put it back:
366
367    >>> converter.toString(u'<NO-VALUE>') is None
368    True
369
370Bool field converters
371---------------------
372
373We create a field that also implements ``IBool``, a regular
374``Bool`` field:
375
376    >>> from zope.schema import Bool
377    >>> field = Bool(title=u'Some truth',
378    ...             default=True,
379    ...             required=True)
380
381Now we can get a converter for this field by explicitly creating a
382:class:`BoolConverter` instance:
383
384    >>> from waeup.sirp.utils.converters import BoolConverter
385    >>> converter = BoolConverter(field)
386
387Or we can just grab a registered adapter:
388
389    >>> from waeup.sirp.interfaces import ISchemaTypeConverter
390    >>> converter = ISchemaTypeConverter(field)
391
392This will select the correct converter for us automatically.
393
394    >>> converter
395    <waeup.sirp.utils.converters.BoolConverter object at 0x...>
396
397Now we can convert strings to this type:
398
399    >>> converter.fromString('yes')
400    True
401
402    >>> converter.fromString('Yes')
403    True
404
405    >>> converter.fromString('yEs')
406    True
407
408    >>> converter.fromString('1')
409    True
410
411    >>> converter.fromString('True')
412    True
413
414    >>> converter.fromString('0')
415    False
416
417    >>> converter.fromString('no')
418    False
419
420    >>> converter.fromString('false')
421    False
422
423
424And back:
425
426    >>> converter.toString(True)
427    '1'
428
429    >>> converter.toString(False)
430    '0'
431
432Okay, not very surprising. But the field definitions can help also
433deliver values, if the given value is missing:
434
435    >>> converter.fromString(None)
436    True
437
438``None`` is not an acceptable value for fields which are required but
439provide no default:
440
441    >>> field = Bool(title=u'Some Truth',
442    ...             required=True)
443
444    >>> converter = ISchemaTypeConverter(field)
445    >>> converter.fromString(None)
446    Traceback (most recent call last):
447    ...
448    RequiredMissing
449
450If we want to avoid this type of exception (and risk non-applicable
451data to be stored), we can use the ``strict`` parameter of
452``fromString()``:
453
454    >>> converter.fromString(None, strict=False) is None
455    True
456
457The same for the inverse operation:
458
459    >>> converter.toString(None)
460    Traceback (most recent call last):
461    ...
462    RequiredMissing
463
464    >>> converter.toString(None, strict=False) is None
465    True
466
467
468If a field is not required, we will get the ``missing_value`` type in
469same case:
470
471    >>> field = Bool(title=u'Some Title',
472    ...             required=False)
473
474    >>> converter = ISchemaTypeConverter(field)
475    >>> converter.fromString(None) is None
476    True
477
478    >>> converter.toString(None) is None
479    True
480
481
482Int field converters
483--------------------
484
485We create a field that also implements ``IInt``, a regular
486``Int`` field:
487
488    >>> from zope.schema import Int
489    >>> field = Int(title=u'Some number',
490    ...             default=12,
491    ...             required=True)
492
493Now we can get a converter for this field by explicitly creating a
494:class:`IntConverter` instance:
495
496    >>> from waeup.sirp.utils.converters import IntConverter
497    >>> converter = IntConverter(field)
498
499Or we can just grab a registered adapter:
500
501    >>> from waeup.sirp.interfaces import ISchemaTypeConverter
502    >>> converter = ISchemaTypeConverter(field)
503
504This will select the correct converter for us automatically.
505
506    >>> converter
507    <waeup.sirp.utils.converters.IntConverter object at 0x...>
508
509Now we can convert strings to this type:
510
511    >>> converter.fromString('666')
512    666
513
514And back:
515
516    >>> converter.toString(666)
517    '666'
518
519Okay, not very surprising. But the field definitions can help also
520deliver values, if the given value is missing:
521
522    >>> converter.fromString(None)
523    12
524
525``None`` is not an acceptable value for fields which are required but
526provide no default:
527
528    >>> field = Int(title=u'Some Title',
529    ...             required=True)
530
531    >>> converter = ISchemaTypeConverter(field)
532    >>> converter.fromString(None)
533    Traceback (most recent call last):
534    ...
535    RequiredMissing
536
537If we want to avoid this type of exception (and risk non-applicable
538data to be stored), we can use the ``strict`` parameter of
539``fromString()``:
540
541    >>> converter.fromString(None, strict=False) is None
542    True
543
544The same for the inverse operation:
545
546    >>> converter.toString(None)
547    Traceback (most recent call last):
548    ...
549    RequiredMissing
550
551    >>> converter.toString(None, strict=False) is None
552    True
553
554
555If a field is not required, we will get the ``missing_value`` type in
556same case:
557
558    >>> field = Int(title=u'Some Title',
559    ...             required=False)
560
561    >>> converter = ISchemaTypeConverter(field)
562    >>> converter.fromString(None) is None
563    True
564
565    >>> converter.toString(None) is None
566    True
567
568
569Choice field converters
570-----------------------
571
572Before choices really work in unit tests, we have to make sure, that
573the following adapters are registered (this is normally done during
574startup automatically):
575
576    >>> from zc.sourcefactory.browser.source import FactoredTerms
577    >>> from zc.sourcefactory.browser.token import (
578    ...   fromString, fromUnicode, fromInteger, fromPersistent)
579    >>> from zope.component import getGlobalSiteManager
580    >>> gsm = getGlobalSiteManager()
581    >>> gsm.registerAdapter(FactoredTerms)
582    >>> gsm.registerAdapter(fromString)
583    >>> gsm.registerAdapter(fromUnicode)
584    >>> gsm.registerAdapter(fromInteger)
585    >>> gsm.registerAdapter(fromPersistent)
586
587We create a field that also implements ``IChoice``, a regular
588``Choice`` field:
589
590    >>> from zope.schema import Choice
591    >>> field = Choice(title=u'Some number',
592    ...                default=u'a',
593    ...                values=['a', 'b', 'c'],
594    ...                required=True)
595
596Now we can get a converter for this field by explicitly creating a
597:class:`ChoiceConverter` instance:
598
599    >>> from waeup.sirp.utils.converters import ChoiceConverter
600    >>> converter = ChoiceConverter(field)
601
602Or we can just grab a registered adapter:
603
604    >>> from waeup.sirp.interfaces import ISchemaTypeConverter
605    >>> converter = ISchemaTypeConverter(field)
606
607This will select the correct converter for us automatically.
608
609    >>> converter
610    <waeup.sirp.utils.converters.ChoiceConverter object at 0x...>
611
612Now we can convert strings to this type:
613
614    >>> converter.fromString('a')
615    'a'
616
617or values back to strings:
618
619    >>> converter.toString('a')
620    'a'
621
622Non-listed values will not be accepted:
623
624    >>> converter.fromString('nonsense')
625    Traceback (most recent call last):
626    ...
627    LookupError: nonsense
628
629Also non-string values will work:
630
631    >>> from zope.schema import Choice
632    >>> field = Choice(title=u'Some number',
633    ...                default=2,
634    ...                values=[1, 2, 3],
635    ...                required=True)
636
637    >>> converter = ISchemaTypeConverter(field)
638    >>> converter.fromString('1')
639    1
640
641If we define an own source, then it will cause no problems:
642
643    >>> from zc.sourcefactory.basic import BasicSourceFactory
644    >>> class CustomSource(BasicSourceFactory):
645    ...   def getValues(self):
646    ...     return [1,2,3]
647    ...   
648    ...   def getTitle(self, value):
649    ...     return "Number %s" % (value,)
650
651    >>> field = Choice(title=u'Some number',
652    ...                default=2,
653    ...                source=CustomSource(),
654    ...                required=True)
655
656    >>> converter = ISchemaTypeConverter(field)
657    >>> converter.fromString('1')
658    1
659
660Note, that when asking for conversion of Choice fields, you must
661deliver the token value of the source/vocabulary. This might be the
662same as the actual value as string, but it it might be different,
663depending of the kind of source/vocabulary used.
664
665Furthermore, when writing 'exporters', i.e. components that turn a
666value into a string for usage in CSV files, then you have to save the
667token value of a term.
668
669Using the :meth:`toString()` method will deliver the token for us:
670
671    >>> converter.toString(1)
672    '1'
673
674    >>> from waeup.sirp.interfaces import SimpleWAeUPVocabulary
675    >>> field = Choice(
676    ...   title = u'Favourite Dish',
677    ...   default = 0,
678    ...   vocabulary = SimpleWAeUPVocabulary(
679    ...        ('N/A', 0), ('Pizza', 1),
680    ...        ('Cake', 2), ('Muffins', 3)),
681    ...   required = True,
682    ...   )
683
684    >>> converter = ISchemaTypeConverter(field)
685    >>> converter.fromString('0')
686    0
687
688    >>> converter.toString(1)
689    '1'
690
691A more complex (but still realistic example) for conversion of
692:mod:`zope.schema.Choice` fields follows. Here we have a special kind
693of object, the `Cave` class, and get their `name` attribute as token.
694
695    >>> class Cave(object):
696    ...   name = 'Home sweet home'
697
698    >>> def tokenizer(cave):
699    ...   return cave.name
700
701The tokenizer has to be registered as an Cave-to-IToken adapter to
702keep :mod:`zc.sourcefactory` happy:
703
704    >>> from zc.sourcefactory.interfaces import IToken
705    >>> from zope.component import getGlobalSiteManager
706    >>> gsm = getGlobalSiteManager()
707    >>> gsm.registerAdapter(tokenizer, required=(Cave,), provided=IToken)
708
709Now we finally can create two objects of `Cave` and make a field, that
710gives the choice between these two objects:
711
712    >>> obj1 = Cave()
713    >>> obj1.name = 'Wilma'
714
715    >>> obj2 = Cave()
716    >>> obj2.name = 'Fred'
717
718    >>> from zc.sourcefactory.basic import BasicSourceFactory
719    >>> class CustomSource(BasicSourceFactory):
720    ...   def getValues(self):
721    ...     return [obj1, obj2]
722    ...   
723    ...   def getTitle(self, value):
724    ...     return value.name
725
726    >>> field = Choice(title=u'Some cave',
727    ...                default=obj1,
728    ...                source=CustomSource(),
729    ...                required=True)
730
731We get a converter for this field in the usual way. The
732:meth:`waeup.sirp.utils.converters.fromString()` method will return one of
733the objects if we feed it with ``'Wilma'`` or ``'Fred'``:
734
735    >>> converter = ISchemaTypeConverter(field)
736    >>> result = converter.fromString('Wilma')
737    >>> result
738    <Cave object at 0x...>
739
740    >>> result is obj1, result.name
741    (True, 'Wilma')
742
743If we use the converter the other way round, it will call the
744tokenizer above and deliver ``'Wilma'`` or ``'Fred'`` as they are the
745tokens of the objects in question:
746
747    >>> converter.toString(obj2)
748    'Fred'
749
750Date field converters
751---------------------
752
753We create a field that also implements ``IDate``, a regular
754``Date`` field:
755
756    >>> from zope.schema import Date
757    >>> from datetime import date
758    >>> field = Date(title=u'Some date',
759    ...             default=date(2010, 4, 1),
760    ...             required=True)
761
762Now we can get a converter for this field by explicitly creating a
763:class:`IntConverter` instance:
764
765    >>> from waeup.sirp.utils.converters import DateConverter
766    >>> converter = DateConverter(field)
767
768Or we can just grab a registered adapter:
769
770    >>> from waeup.sirp.interfaces import ISchemaTypeConverter
771    >>> converter = ISchemaTypeConverter(field)
772
773This will select the correct converter for us automatically.
774
775    >>> converter
776    <waeup.sirp.utils.converters.DateConverter object at 0x...>
777
778Now we can convert strings to this type:
779
780    >>> converter.fromString('2007-04-23')
781    datetime.date(2007, 4, 23)
782
783And back:
784
785    >>> converter.toString(date(2007,4,23))
786    '2007-04-23'
787
788As string input we also accept 'DD/MM/YYYY':
789
790    >>> converter.fromString('13/05/1945')
791    datetime.date(1945, 5, 13)
792
793and some more bizarre formats:
794
795    >>> converter.fromString('1945/05/13')
796    datetime.date(1945, 5, 13)
797
798    >>> converter.fromString('1945/05/13 12:12:34 GMT+1')
799    datetime.date(1945, 5, 13)
800
801    >>> converter.fromString('1945/5/6')
802    datetime.date(1945, 5, 6)
803
804    >>> converter.fromString('06.05.1945')
805    datetime.date(1945, 5, 6)
806
807    >>> converter.fromString('6.5.1945')
808    datetime.date(1945, 5, 6)
809
810while other date formats will mean trouble:
811
812    >>> converter.fromString('first of July 1960')
813    Traceback (most recent call last):
814    ...
815    ValueError: Cannot convert to date: first of July 1960. Use YYYY-MM-DD.
816
817Okay, not very surprising. But the field definitions can help also
818deliver values, if the given value is missing:
819
820    >>> converter.fromString(None)
821    datetime.date(2010, 4, 1)
822
823``None`` is not an acceptable value for fields which are required but
824provide no default:
825
826    >>> field = Date(title=u'Some Title',
827    ...             required=True)
828    >>> converter = ISchemaTypeConverter(field)
829    >>> converter.fromString(None)
830    Traceback (most recent call last):
831    ...
832    RequiredMissing
833
834If we want to avoid this type of exception (and risk non-applicable
835data to be stored), we can use the ``strict`` parameter of
836``fromString()``:
837
838    >>> converter.fromString(None, strict=False) is None
839    True
840
841The same for the inverse operation:
842
843    >>> converter.toString(None)
844    Traceback (most recent call last):
845    ...
846    RequiredMissing
847    >>> converter.toString(None, strict=False) is None
848    True
849
850
851If a field is not required, we will get the ``missing_value`` type in
852same case:
853
854    >>> field = Date(title=u'Some Title',
855    ...             required=False)
856    >>> converter = ISchemaTypeConverter(field)
857    >>> converter.fromString(None) is None
858    True
859
860    >>> converter.toString(None) is None
861    True
Note: See TracBrowser for help on using the repository browser.