1#!/usr/bin/env python
2# coding=utf-8
3#
4# spyne - Copyright (C) Spyne contributors.
5#
6# This library is free software; you can redistribute it and/or
7# modify it under the terms of the GNU Lesser General Public
8# License as published by the Free Software Foundation; either
9# version 2.1 of the License, or (at your option) any later version.
10#
11# This library is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14# Lesser General Public License for more details.
15#
16# You should have received a copy of the GNU Lesser General Public
17# License along with this library; if not, write to the Free Software
18# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
19#
20
21from __future__ import print_function
22
23import re
24import uuid
25import datetime
26import unittest
27import warnings
28
29import pytz
30import spyne
31
32from datetime import timedelta
33
34from lxml import etree
35from spyne.model.primitive._base import re_match_with_span as rmws
36
37from spyne.util import six
38from spyne.const import xml as ns
39
40from spyne import Null, AnyDict, Uuid, Array, ComplexModel, Date, Time, \
41    Boolean, DateTime, Duration, Float, Integer, NumberLimitsWarning, Unicode, \
42    String, Decimal, Integer16, ModelBase, MimeType, MimeTypeStrict, MediaType
43
44from spyne.protocol import ProtocolBase
45from spyne.protocol.xml import XmlDocument
46
47ns_test = 'test_namespace'
48
49
50class TestCast(unittest.TestCase):
51    pass  # TODO: test Unicode(cast=str)
52
53
54class TestPrimitive(unittest.TestCase):
55    def test_mime_type_family(self):
56        mime_attr = MimeType.Attributes
57        mime_strict_attr = MimeTypeStrict.Attributes
58        assert     rmws(mime_attr, u'application/foo')
59        assert not rmws(mime_attr, u'application/ foo')
60        assert not rmws(mime_attr, u'application/')
61        assert     rmws(mime_attr, u'foo/bar')
62        assert not rmws(mime_attr, u'foo/bar ')
63        assert not rmws(mime_strict_attr, u'foo/bar')
64
65        media_attr = MediaType.Attributes
66        media_strict_attr = MediaType.Attributes
67
68        print(media_attr.pattern)
69
70        assert     rmws(media_attr, u'text/plain; charset="utf-8"')
71        assert     rmws(media_attr, u'text/plain; charset=utf-8')
72        assert     rmws(media_attr, u'text/plain; charset=utf-8 ')
73        assert     rmws(media_attr, u'text/plain; charset=utf-8')
74        assert     rmws(media_attr, u'text/plain; charset=utf-8;')
75        assert     rmws(media_attr, u'text/plain; charset=utf-8; ')
76        assert not rmws(media_attr, u'text/plain; charset=utf-8; foo')
77        assert not rmws(media_attr, u'text/plain; charset=utf-8; foo=')
78        assert     rmws(media_attr, u'text/plain; charset=utf-8; foo=""')
79        assert     rmws(media_attr, u'text/plain; charset=utf-8; foo="";')
80        assert     rmws(media_attr, u'text/plain; charset=utf-8; foo=""; ')
81        assert     rmws(media_attr, u'text/plain; charset=utf-8; foo="";  ')
82        assert not rmws(media_attr, u'text/plain;; charset=utf-8; foo=""')
83        assert not rmws(media_attr, u'text/plain;;; charset=utf-8; foo=""')
84        assert not rmws(media_attr, u'text/plain; charset=utf-8;; foo=""')
85        assert not rmws(media_attr, u'text/plain; charset=utf-8;;; foo=""')
86        assert not rmws(media_attr, u'text/plain; charset=utf-8;;; foo="";')
87        assert not rmws(media_attr, u'text/plain; charset=utf-8;;; foo=""; ; ')
88        assert not rmws(media_strict_attr, u'foo/bar;')
89        assert not rmws(media_strict_attr, u' applicaton/json;')
90        assert not rmws(media_strict_attr, u'applicaton/json;')
91
92        assert MediaType
93
94
95    def test_getitem_cust(self):
96        assert Unicode[dict(max_len=2)].Attributes.max_len
97
98    def test_ancestors(self):
99        class A(ComplexModel): i = Integer
100        class B(A): i2 = Integer
101        class C(B): i3 = Integer
102
103        assert C.ancestors() == [B, A]
104        assert B.ancestors() == [A]
105        assert A.ancestors() == []
106
107    def test_nillable_quirks(self):
108        assert ModelBase.Attributes.nillable == True
109
110        class Attributes(ModelBase.Attributes):
111            nillable = False
112            nullable = False
113
114        assert Attributes.nillable == False
115        assert Attributes.nullable == False
116
117        class Attributes(ModelBase.Attributes):
118            nillable = True
119
120        assert Attributes.nillable == True
121        assert Attributes.nullable == True
122
123        class Attributes(ModelBase.Attributes):
124            nillable = False
125
126        assert Attributes.nillable == False
127        assert Attributes.nullable == False
128
129        class Attributes(ModelBase.Attributes):
130            nullable = True
131
132        assert Attributes.nillable == True
133        assert Attributes.nullable == True
134
135        class Attributes(ModelBase.Attributes):
136            nullable = False
137
138        assert Attributes.nillable == False
139        assert Attributes.nullable == False
140
141        class Attributes(ModelBase.Attributes):
142            nullable = False
143
144        class Attributes(Attributes):
145            pass
146
147        assert Attributes.nullable == False
148
149    def test_nillable_inheritance_quirks(self):
150        class Attributes(ModelBase.Attributes):
151            nullable = False
152
153        class AttrMixin:
154            pass
155
156        class NewAttributes(Attributes, AttrMixin):
157            pass
158
159        assert NewAttributes.nullable is False
160
161        class AttrMixin:
162            pass
163
164        class NewAttributes(AttrMixin, Attributes):
165            pass
166
167        assert NewAttributes.nullable is False
168
169    def test_decimal(self):
170        assert Decimal(10, 4).Attributes.total_digits == 10
171        assert Decimal(10, 4).Attributes.fraction_digits == 4
172
173    def test_decimal_format(self):
174        f = 123456
175        str_format = '${0}'
176        element = etree.Element('test')
177        XmlDocument().to_parent(None, Decimal(str_format=str_format), f,
178                                                               element, ns_test)
179        element = element[0]
180
181        self.assertEqual(element.text, '$123456')
182
183    def test_string(self):
184        s = String()
185        element = etree.Element('test')
186        XmlDocument().to_parent(None, String, 'value', element, ns_test)
187        element = element[0]
188
189        self.assertEqual(element.text, 'value')
190        value = XmlDocument().from_element(None, String, element)
191        self.assertEqual(value, 'value')
192
193    def test_datetime(self):
194        n = datetime.datetime.now(pytz.utc)
195
196        element = etree.Element('test')
197        XmlDocument().to_parent(None, DateTime, n, element, ns_test)
198        element = element[0]
199
200        self.assertEqual(element.text, n.isoformat())
201        dt = XmlDocument().from_element(None, DateTime, element)
202        self.assertEqual(n, dt)
203
204    def test_datetime_format(self):
205        n = datetime.datetime.now().replace(microsecond=0)
206        format = "%Y %m %d %H %M %S"
207
208        element = etree.Element('test')
209        XmlDocument().to_parent(None, DateTime(dt_format=format), n, element,
210                                                                        ns_test)
211        element = element[0]
212
213        assert element.text == datetime.datetime.strftime(n, format)
214        dt = XmlDocument().from_element(None, DateTime(dt_format=format),
215                                                                        element)
216        assert n == dt
217
218    def test_datetime_unicode_format(self):
219        n = datetime.datetime.now().replace(microsecond=0)
220        format = u"%Y %m %d\u00a0%H %M %S"
221
222        element = etree.Element('test')
223        XmlDocument().to_parent(None, DateTime(dt_format=format), n,
224                                                               element, ns_test)
225        element = element[0]
226
227        if six.PY2:
228            assert element.text == n.strftime(format.encode('utf8')) \
229                                                                 .decode('utf8')
230        else:
231            assert element.text == n.strftime(format)
232
233        dt = XmlDocument().from_element(None, DateTime(dt_format=format),
234                                                                        element)
235        assert n == dt
236
237    def test_date_format(self):
238        t = datetime.date.today()
239        format = "%Y-%m-%d"
240
241        element = etree.Element('test')
242        XmlDocument().to_parent(None,
243                                  Date(date_format=format), t, element, ns_test)
244        assert element[0].text == datetime.date.strftime(t, format)
245
246        dt = XmlDocument().from_element(None,
247                                           Date(date_format=format), element[0])
248        assert t == dt
249
250    def test_datetime_timezone(self):
251        import pytz
252
253        n = datetime.datetime.now(pytz.timezone('EST'))
254        element = etree.Element('test')
255        cls = DateTime(as_timezone=pytz.utc, timezone=False)
256        XmlDocument().to_parent(None, cls, n, element, ns_test)
257        element = element[0]
258
259        c = n.astimezone(pytz.utc).replace(tzinfo=None)
260        self.assertEqual(element.text, c.isoformat())
261        dt = XmlDocument().from_element(None, cls, element)
262        assert dt.tzinfo is not None
263        dt = dt.replace(tzinfo=None)
264        self.assertEqual(c, dt)
265
266    def test_date_timezone(self):
267        elt = etree.Element('wot')
268        elt.text = '2013-08-09+02:00'
269        dt = XmlDocument().from_element(None, Date, elt)
270        print("ok without validation.")
271        dt = XmlDocument(validator='soft').from_element(None, Date, elt)
272        print(dt)
273
274    def test_time(self):
275        n = datetime.time(1, 2, 3, 4)
276
277        ret = ProtocolBase().to_bytes(Time, n)
278        self.assertEqual(ret, n.isoformat())
279
280        dt = ProtocolBase().from_unicode(Time, ret)
281        self.assertEqual(n, dt)
282
283    def test_time_usec(self):
284        # python's datetime and time only accept ints between [0, 1e6[
285        # if the incoming data is 999999.8 microseconds, rounding it up means
286        # adding 1 second to time. For many reasons, we want to avoid that. (see
287        # http://bugs.python.org/issue1487389) That's why 999999.8 usec is
288        # rounded to 999999.
289
290        # rounding 0.1 µsec down
291        t = ProtocolBase().from_unicode(Time, "12:12:12.0000001")
292        self.assertEqual(datetime.time(12, 12, 12), t)
293
294        # rounding 1.5 µsec up. 0.5 is rounded down by python 3 and up by
295        # python 2 so we test with 1.5 µsec instead. frikkin' nonsense.
296        t = ProtocolBase().from_unicode(Time, "12:12:12.0000015")
297        self.assertEqual(datetime.time(12, 12, 12, 2), t)
298
299        # rounding 999998.8 µsec up
300        t = ProtocolBase().from_unicode(Time, "12:12:12.9999988")
301        self.assertEqual(datetime.time(12, 12, 12, 999999), t)
302
303        # rounding 999999.1 µsec down
304        t = ProtocolBase().from_unicode(Time, "12:12:12.9999991")
305        self.assertEqual(datetime.time(12, 12, 12, 999999), t)
306
307        # rounding 999999.8 µsec down, not up.
308        t = ProtocolBase().from_unicode(Time, "12:12:12.9999998")
309        self.assertEqual(datetime.time(12, 12, 12, 999999), t)
310
311    def test_date(self):
312        n = datetime.date(2011, 12, 13)
313
314        ret = ProtocolBase().to_unicode(Date, n)
315        self.assertEqual(ret, n.isoformat())
316
317        dt = ProtocolBase().from_unicode(Date, ret)
318        self.assertEqual(n, dt)
319
320    def test_utcdatetime(self):
321        datestring = '2007-05-15T13:40:44Z'
322        e = etree.Element('test')
323        e.text = datestring
324
325        dt = XmlDocument().from_element(None, DateTime, e)
326
327        self.assertEqual(dt.year, 2007)
328        self.assertEqual(dt.month, 5)
329        self.assertEqual(dt.day, 15)
330
331        datestring = '2007-05-15T13:40:44.003Z'
332        e = etree.Element('test')
333        e.text = datestring
334
335        dt = XmlDocument().from_element(None, DateTime, e)
336
337        self.assertEqual(dt.year, 2007)
338        self.assertEqual(dt.month, 5)
339        self.assertEqual(dt.day, 15)
340
341    def test_date_exclusive_boundaries(self):
342        test_model = Date.customize(gt=datetime.date(2016, 1, 1),
343            lt=datetime.date(2016, 2, 1))
344        self.assertFalse(
345              test_model.validate_native(test_model, datetime.date(2016, 1, 1)))
346        self.assertFalse(
347              test_model.validate_native(test_model, datetime.date(2016, 2, 1)))
348
349    def test_date_inclusive_boundaries(self):
350        test_model = Date.customize(ge=datetime.date(2016, 1, 1),
351            le=datetime.date(2016, 2, 1))
352        self.assertTrue(
353              test_model.validate_native(test_model, datetime.date(2016, 1, 1)))
354        self.assertTrue(
355              test_model.validate_native(test_model, datetime.date(2016, 2, 1)))
356
357    def test_datetime_exclusive_boundaries(self):
358        test_model = DateTime.customize(
359            gt=datetime.datetime(2016, 1, 1, 12, 00)
360                                                .replace(tzinfo=spyne.LOCAL_TZ),
361            lt=datetime.datetime(2016, 2, 1, 12, 00)
362                                                .replace(tzinfo=spyne.LOCAL_TZ),
363        )
364        self.assertFalse(test_model.validate_native(test_model,
365                                         datetime.datetime(2016, 1, 1, 12, 00)))
366        self.assertFalse(test_model.validate_native(test_model,
367                                         datetime.datetime(2016, 2, 1, 12, 00)))
368
369    def test_datetime_inclusive_boundaries(self):
370        test_model = DateTime.customize(
371            ge=datetime.datetime(2016, 1, 1, 12, 00)
372                                                .replace(tzinfo=spyne.LOCAL_TZ),
373            le=datetime.datetime(2016, 2, 1, 12, 00)
374                                                .replace(tzinfo=spyne.LOCAL_TZ)
375        )
376
377        self.assertTrue(test_model.validate_native(test_model,
378                                         datetime.datetime(2016, 1, 1, 12, 00)))
379        self.assertTrue(test_model.validate_native(test_model,
380                                         datetime.datetime(2016, 2, 1, 12, 00)))
381
382    def test_time_exclusive_boundaries(self):
383        test_model = Time.customize(gt=datetime.time(12, 00),
384                                                       lt=datetime.time(13, 00))
385
386        self.assertFalse(
387            test_model.validate_native(test_model, datetime.time(12, 00)))
388        self.assertFalse(
389            test_model.validate_native(test_model, datetime.time(13, 00)))
390
391    def test_time_inclusive_boundaries(self):
392        test_model = Time.customize(ge=datetime.time(12, 00),
393                                                       le=datetime.time(13, 00))
394
395        self.assertTrue(
396                  test_model.validate_native(test_model, datetime.time(12, 00)))
397        self.assertTrue(
398                  test_model.validate_native(test_model, datetime.time(13, 00)))
399
400    def test_datetime_extreme_boundary(self):
401        self.assertTrue(
402                      DateTime.validate_native(DateTime, datetime.datetime.min))
403        self.assertTrue(
404                      DateTime.validate_native(DateTime, datetime.datetime.max))
405
406    def test_time_extreme_boundary(self):
407        self.assertTrue(Time.validate_native(Time, datetime.time(0, 0, 0, 0)))
408        self.assertTrue(
409                  Time.validate_native(Time, datetime.time(23, 59, 59, 999999)))
410
411    def test_date_extreme_boundary(self):
412        self.assertTrue(Date.validate_native(Date, datetime.date.min))
413        self.assertTrue(Date.validate_native(Date, datetime.date.max))
414
415    def test_integer(self):
416        i = 12
417        integer = Integer()
418
419        element = etree.Element('test')
420        XmlDocument().to_parent(None, Integer, i, element, ns_test)
421        element = element[0]
422
423        self.assertEqual(element.text, '12')
424        value = XmlDocument().from_element(None, integer, element)
425        self.assertEqual(value, i)
426
427    def test_integer_limits(self):
428        with warnings.catch_warnings(record=True) as w:
429            warnings.simplefilter("always")
430
431            integer = Integer16(ge=-32768)
432
433            assert len(w) == 0
434
435            integer = Integer16(ge=-32769)
436            assert len(w) == 1
437            assert issubclass(w[-1].category, NumberLimitsWarning)
438            assert "smaller than min_bound" in str(w[-1].message)
439
440        with warnings.catch_warnings(record=True) as w:
441            warnings.simplefilter("always")
442
443            integer = Integer16(le=32767)
444
445            assert len(w) == 0
446
447            integer = Integer16(le=32768)
448            assert len(w) == 1
449            assert issubclass(w[-1].category, NumberLimitsWarning)
450            assert "greater than max_bound" in str(w[-1].message)
451
452        try:
453            Integer16(ge=32768)
454        except ValueError:
455            pass
456        else:
457            raise Exception("must fail")
458
459        try:
460            Integer16(lt=-32768)
461        except ValueError:
462            pass
463        else:
464            raise Exception("must fail")
465
466    def test_large_integer(self):
467        i = 128375873458473
468        integer = Integer()
469
470        element = etree.Element('test')
471        XmlDocument().to_parent(None, Integer, i, element, ns_test)
472        element = element[0]
473
474        self.assertEqual(element.text, '128375873458473')
475        value = XmlDocument().from_element(None, integer, element)
476        self.assertEqual(value, i)
477
478    def test_float(self):
479        f = 1.22255645
480
481        element = etree.Element('test')
482        XmlDocument().to_parent(None, Float, f, element, ns_test)
483        element = element[0]
484
485        self.assertEqual(element.text, repr(f))
486
487        f2 = XmlDocument().from_element(None, Float, element)
488        self.assertEqual(f2, f)
489
490    def test_array(self):
491        type = Array(String)
492        type.resolve_namespace(type, "zbank")
493
494        values = ['a', 'b', 'c', 'd', 'e', 'f']
495
496        element = etree.Element('test')
497        XmlDocument().to_parent(None, type, values, element, ns_test)
498        element = element[0]
499
500        self.assertEqual(len(values), len(element.getchildren()))
501
502        values2 = XmlDocument().from_element(None, type, element)
503        self.assertEqual(values[3], values2[3])
504
505    def test_array_empty(self):
506        type = Array(String)
507        type.resolve_namespace(type, "zbank")
508
509        values = []
510
511        element = etree.Element('test')
512        XmlDocument().to_parent(None, type, values, element, ns_test)
513        element = element[0]
514
515        self.assertEqual(len(values), len(element.getchildren()))
516
517        values2 = XmlDocument().from_element(None, type, element)
518        self.assertEqual(len(values2), 0)
519
520    def test_unicode(self):
521        s = u'\x34\x55\x65\x34'
522        self.assertEqual(4, len(s))
523        element = etree.Element('test')
524        XmlDocument().to_parent(None, String, s, element, 'test_ns')
525        element = element[0]
526        value = XmlDocument().from_element(None, String, element)
527        self.assertEqual(value, s)
528
529    def test_unicode_pattern_mult_cust(self):
530        assert Unicode(pattern='a').Attributes.pattern == 'a'
531        assert Unicode(pattern='a')(5).Attributes.pattern == 'a'
532
533    def test_unicode_upattern(self):
534        patt = r'[\w .-]+'
535        attr = Unicode(unicode_pattern=patt).Attributes
536        assert attr.pattern == patt
537        assert attr._pattern_re.flags & re.UNICODE
538        assert attr._pattern_re.match(u"Ğ Ğ ç .-")
539        assert attr._pattern_re.match(u"\t") is None
540
541    def test_unicode_nullable_mult_cust_false(self):
542        assert Unicode(nullable=False).Attributes.nullable == False
543        assert Unicode(nullable=False)(5).Attributes.nullable == False
544
545    def test_unicode_nullable_mult_cust_true(self):
546        assert Unicode(nullable=True).Attributes.nullable == True
547        assert Unicode(nullable=True)(5).Attributes.nullable == True
548
549    def test_null(self):
550        element = etree.Element('test')
551        XmlDocument().to_parent(None, Null, None, element, ns_test)
552        print(etree.tostring(element))
553
554        element = element[0]
555        self.assertTrue(bool(element.attrib.get(ns.XSI('nil'))))
556        value = XmlDocument().from_element(None, Null, element)
557        self.assertEqual(None, value)
558
559    def test_point(self):
560        from spyne.model.primitive.spatial import _get_point_pattern
561
562        a = re.compile(_get_point_pattern(2))
563        assert a.match('POINT (10 40)') is not None
564        assert a.match('POINT(10 40)') is not None
565
566        assert a.match('POINT(10.0 40)') is not None
567        assert a.match('POINT(1.310e4 40)') is not None
568
569    def test_multipoint(self):
570        from spyne.model.primitive.spatial import _get_multipoint_pattern
571
572        a = re.compile(_get_multipoint_pattern(2))
573        assert a.match('MULTIPOINT (10 40, 40 30, 20 20, 30 10)') is not None
574        # FIXME:
575        # assert a.match('MULTIPOINT ((10 40), (40 30), (20 20), (30 10))') is not None
576
577    def test_linestring(self):
578        from spyne.model.primitive.spatial import _get_linestring_pattern
579
580        a = re.compile(_get_linestring_pattern(2))
581        assert a.match('LINESTRING (30 10, 10 30, 40 40)') is not None
582
583    def test_multilinestring(self):
584        from spyne.model.primitive.spatial import _get_multilinestring_pattern
585
586        a = re.compile(_get_multilinestring_pattern(2))
587        assert a.match('''MULTILINESTRING ((10 10, 20 20, 10 40),
588                                (40 40, 30 30, 40 20, 30 10))''') is not None
589
590    def test_polygon(self):
591        from spyne.model.primitive.spatial import _get_polygon_pattern
592
593        a = re.compile(_get_polygon_pattern(2))
594        assert a.match(
595                    'POLYGON ((30 10, 10 20, 20 40, 40 40, 30 10))') is not None
596
597    def test_multipolygon(self):
598        from spyne.model.primitive.spatial import _get_multipolygon_pattern
599
600        a = re.compile(_get_multipolygon_pattern(2))
601        assert a.match('''MULTIPOLYGON (((30 20, 10 40, 45 40, 30 20)),
602                            ((15 5, 40 10, 10 20, 5 10, 15 5)))''') is not None
603        assert a.match('''MULTIPOLYGON (((40 40, 20 45, 45 30, 40 40)),
604                                ((20 35, 45 20, 30 5, 10 10, 10 30, 20 35),
605                                (30 20, 20 25, 20 15, 30 20)))''') is not None
606
607    def test_boolean(self):
608        b = etree.Element('test')
609        XmlDocument().to_parent(None, Boolean, True, b, ns_test)
610        b = b[0]
611        self.assertEqual('true', b.text)
612
613        b = etree.Element('test')
614        XmlDocument().to_parent(None, Boolean, 0, b, ns_test)
615        b = b[0]
616        self.assertEqual('false', b.text)
617
618        b = etree.Element('test')
619        XmlDocument().to_parent(None, Boolean, 1, b, ns_test)
620        b = b[0]
621        self.assertEqual('true', b.text)
622
623        b = XmlDocument().from_element(None, Boolean, b)
624        self.assertEqual(b, True)
625
626        b = etree.Element('test')
627        XmlDocument().to_parent(None, Boolean, False, b, ns_test)
628        b = b[0]
629        self.assertEqual('false', b.text)
630
631        b = XmlDocument().from_element(None, Boolean, b)
632        self.assertEqual(b, False)
633
634        b = etree.Element('test')
635        XmlDocument().to_parent(None, Boolean, None, b, ns_test)
636        b = b[0]
637        self.assertEqual('true', b.get(ns.XSI('nil')))
638
639        b = XmlDocument().from_element(None, Boolean, b)
640        self.assertEqual(b, None)
641
642    def test_new_type(self):
643        """Customized primitives go into namespace based on module name."""
644        custom_type = Unicode(pattern='123')
645        self.assertEqual(custom_type.get_namespace(), custom_type.__module__)
646
647    def test_default_nullable(self):
648        """Test if default nullable changes nullable attribute."""
649        try:
650            self.assertTrue(Unicode.Attributes.nullable)
651            orig_default = Unicode.Attributes.NULLABLE_DEFAULT
652            Unicode.Attributes.NULLABLE_DEFAULT = False
653            self.assertFalse(Unicode.Attributes.nullable)
654            self.assertFalse(Unicode.Attributes.nillable)
655        finally:
656            Unicode.Attributes.NULLABLE_DEFAULT = orig_default
657            self.assertEqual(Unicode.Attributes.nullable, orig_default)
658
659    def test_simple_type_explicit_customization(self):
660        assert Unicode(max_len=5).__extends__ is not None
661        assert Unicode.customize(max_len=5).__extends__ is not None
662
663    def test_anydict_customization(self):
664        from spyne.model import json
665        assert isinstance(
666                   AnyDict.customize(store_as='json').Attributes.store_as, json)
667
668    def test_uuid_serialize(self):
669        value = uuid.UUID('12345678123456781234567812345678')
670
671        assert ProtocolBase().to_unicode(Uuid, value) \
672                             == '12345678-1234-5678-1234-567812345678'
673        assert ProtocolBase().to_unicode(Uuid(serialize_as='hex'), value) \
674                             == '12345678123456781234567812345678'
675        assert ProtocolBase().to_unicode(Uuid(serialize_as='urn'), value) \
676                             == 'urn:uuid:12345678-1234-5678-1234-567812345678'
677        assert ProtocolBase().to_unicode(Uuid(serialize_as='bytes'), value) \
678                             == b'\x124Vx\x124Vx\x124Vx\x124Vx'
679        assert ProtocolBase().to_unicode(Uuid(serialize_as='bytes_le'), value) \
680                             == b'xV4\x124\x12xV\x124Vx\x124Vx'
681        assert ProtocolBase().to_unicode(Uuid(serialize_as='fields'), value) \
682                             == (305419896, 4660, 22136, 18, 52, 95073701484152)
683        assert ProtocolBase().to_unicode(Uuid(serialize_as='int'), value) \
684                             == 24197857161011715162171839636988778104
685
686    def test_uuid_deserialize(self):
687        value = uuid.UUID('12345678123456781234567812345678')
688
689        assert ProtocolBase().from_unicode(Uuid,
690                '12345678-1234-5678-1234-567812345678') == value
691        assert ProtocolBase().from_unicode(Uuid(serialize_as='hex'),
692                '12345678123456781234567812345678') == value
693        assert ProtocolBase().from_unicode(Uuid(serialize_as='urn'),
694                'urn:uuid:12345678-1234-5678-1234-567812345678') == value
695        assert ProtocolBase().from_bytes(Uuid(serialize_as='bytes'),
696                b'\x124Vx\x124Vx\x124Vx\x124Vx') == value
697        assert ProtocolBase().from_bytes(Uuid(serialize_as='bytes_le'),
698                b'xV4\x124\x12xV\x124Vx\x124Vx') == value
699        assert ProtocolBase().from_unicode(Uuid(serialize_as='fields'),
700                (305419896, 4660, 22136, 18, 52, 95073701484152)) == value
701        assert ProtocolBase().from_unicode(Uuid(serialize_as='int'),
702                24197857161011715162171839636988778104) == value
703
704    def test_uuid_validate(self):
705        assert Uuid.validate_string(Uuid,
706                          '12345678-1234-5678-1234-567812345678')
707        assert Uuid.validate_native(Uuid,
708                uuid.UUID('12345678-1234-5678-1234-567812345678'))
709
710    def test_datetime_serialize_as(self):
711        i = 1234567890123456
712        v = datetime.datetime.fromtimestamp(i / 1e6)
713
714        assert ProtocolBase().to_unicode(
715                                DateTime(serialize_as='sec'), v) == i//1e6
716        assert ProtocolBase().to_unicode(
717                                DateTime(serialize_as='sec_float'), v) == i/1e6
718        assert ProtocolBase().to_unicode(
719                                DateTime(serialize_as='msec'), v) == i//1e3
720        assert ProtocolBase().to_unicode(
721                                DateTime(serialize_as='msec_float'), v) == i/1e3
722        assert ProtocolBase().to_unicode(
723                                DateTime(serialize_as='usec'), v) == i
724
725    def test_datetime_deserialize(self):
726        i = 1234567890123456
727        v = datetime.datetime.fromtimestamp(i / 1e6)
728
729        assert ProtocolBase().from_unicode(
730                    DateTime(serialize_as='sec'), i//1e6) == \
731                                     datetime.datetime.fromtimestamp(i//1e6)
732        assert ProtocolBase().from_unicode(
733                    DateTime(serialize_as='sec_float'), i/1e6) == v
734
735        assert ProtocolBase().from_unicode(
736                    DateTime(serialize_as='msec'), i//1e3) == \
737                                    datetime.datetime.fromtimestamp(i/1e3//1000)
738        assert ProtocolBase().from_unicode(
739                    DateTime(serialize_as='msec_float'), i/1e3) == v
740
741        assert ProtocolBase().from_unicode(
742                    DateTime(serialize_as='usec'), i) == v
743
744    def test_datetime_ancient(self):
745        t = DateTime(dt_format="%Y-%m-%d %H:%M:%S")  # to trigger strftime
746        v = datetime.datetime(1881, 1, 1)
747        vs = '1881-01-01 00:00:00'
748
749        dt = ProtocolBase().from_unicode(t, vs)
750        self.assertEqual(v, dt)
751
752        dt = ProtocolBase().to_unicode(t, v)
753        self.assertEqual(vs, dt)
754
755    def test_custom_strftime(self):
756        s = ProtocolBase.strftime(datetime.date(1800, 9, 23),
757                                        "%Y has the same days as 1980 and 2008")
758        if s != "1800 has the same days as 1980 and 2008":
759            raise AssertionError(s)
760
761        print("Testing all day names from 0001/01/01 until 2000/08/01")
762        # Get the weekdays.  Can't hard code them; they could be
763        # localized.
764        days = []
765        for i in range(1, 10):
766            days.append(datetime.date(2000, 1, i).strftime("%A"))
767        nextday = {}
768        for i in range(8):
769            nextday[days[i]] = days[i + 1]
770
771        startdate = datetime.date(1, 1, 1)
772        enddate = datetime.date(2000, 8, 1)
773        prevday = ProtocolBase.strftime(startdate, "%A")
774        one_day = datetime.timedelta(1)
775
776        testdate = startdate + one_day
777        while testdate < enddate:
778            if (testdate.day == 1 and testdate.month == 1 and
779                                                    (testdate.year % 100 == 0)):
780                print("Testing century", testdate.year)
781            day = ProtocolBase.strftime(testdate, "%A")
782            if nextday[prevday] != day:
783                raise AssertionError(str(testdate))
784            prevday = day
785            testdate = testdate + one_day
786
787    def test_datetime_usec(self):
788        # see the comments on time test for why the rounding here is weird
789
790        # rounding 0.1 µsec down
791        dt = ProtocolBase().from_unicode(DateTime,
792                                                  "2015-01-01 12:12:12.0000001")
793        self.assertEqual(datetime.datetime(2015, 1, 1, 12, 12, 12), dt)
794
795        # rounding 1.5 µsec up. 0.5 is rounded down by python 3 and up by
796        # python 2 so we test with 1.5 µsec instead. frikkin' nonsense.
797        dt = ProtocolBase().from_unicode(DateTime,
798                                                  "2015-01-01 12:12:12.0000015")
799        self.assertEqual(datetime.datetime(2015, 1, 1, 12, 12, 12, 2), dt)
800
801        # rounding 999998.8 µsec up
802        dt = ProtocolBase().from_unicode(DateTime,
803                                                  "2015-01-01 12:12:12.9999988")
804        self.assertEqual(datetime.datetime(2015, 1, 1, 12, 12, 12, 999999), dt)
805
806        # rounding 999999.1 µsec down
807        dt = ProtocolBase().from_unicode(DateTime,
808                                                  "2015-01-01 12:12:12.9999991")
809        self.assertEqual(datetime.datetime(2015, 1, 1, 12, 12, 12, 999999), dt)
810
811        # rounding 999999.8 µsec down, not up.
812        dt = ProtocolBase().from_unicode(DateTime,
813                                                  "2015-01-01 12:12:12.9999998")
814        self.assertEqual(datetime.datetime(2015, 1, 1, 12, 12, 12, 999999), dt)
815
816
817### Duration Data Type
818## http://www.w3schools.com/schema/schema_dtypes_date.asp
819# Duration Data type
820#  The time interval is specified in the following form "PnYnMnDTnHnMnS" where:
821# P indicates the period (required)
822# nY indicates the number of years
823# nM indicates the number of months
824# nD indicates the number of days
825# T indicates the start of a time section (*required* if you are going to
826#                               specify hours, minutes, seconds or microseconds)
827# nH indicates the number of hours
828# nM indicates the number of minutes
829# nS indicates the number of seconds
830
831class SomeBlob(ComplexModel):
832    __namespace__ = 'myns'
833    howlong = Duration()
834
835
836class TestDurationPrimitive(unittest.TestCase):
837    def test_onehour_oneminute_onesecond(self):
838        answer = 'PT1H1M1S'
839        gg = SomeBlob()
840        gg.howlong = timedelta(hours=1, minutes=1, seconds=1)
841
842        element = etree.Element('test')
843        XmlDocument().to_parent(None, SomeBlob, gg, element, gg.get_namespace())
844        element = element[0]
845
846        print(gg.howlong)
847        print(etree.tostring(element, pretty_print=True))
848        assert element[0].text == answer
849
850        data = element.find('{%s}howlong' % gg.get_namespace()).text
851        self.assertEqual(data, answer)
852        s1 = XmlDocument().from_element(None, SomeBlob, element)
853        assert s1.howlong.total_seconds() == gg.howlong.total_seconds()
854
855    def test_4suite(self):
856        # borrowed from 4Suite
857        tests_seconds = [
858            (0, u'PT0S'),
859            (1, u'PT1S'),
860            (59, u'PT59S'),
861            (60, u'PT1M'),
862            (3599, u'PT59M59S'),
863            (3600, u'PT1H'),
864            (86399, u'PT23H59M59S'),
865            (86400, u'P1D'),
866            (86400 * 60, u'P60D'),
867            (86400 * 400, u'P400D')
868        ]
869
870        for secs, answer in tests_seconds:
871            gg = SomeBlob()
872            gg.howlong = timedelta(seconds=secs)
873
874            element = etree.Element('test')
875            XmlDocument()\
876                     .to_parent(None, SomeBlob, gg, element, gg.get_namespace())
877            element = element[0]
878
879            print(gg.howlong)
880            print(etree.tostring(element, pretty_print=True))
881            assert element[0].text == answer
882
883            data = element.find('{%s}howlong' % gg.get_namespace()).text
884            self.assertEqual(data, answer)
885            s1 = XmlDocument().from_element(None, SomeBlob, element)
886            assert s1.howlong.total_seconds() == secs
887
888        for secs, answer in tests_seconds:
889            if secs > 0:
890                secs *= -1
891                answer = '-' + answer
892                gg = SomeBlob()
893                gg.howlong = timedelta(seconds=secs)
894
895                element = etree.Element('test')
896                XmlDocument()\
897                     .to_parent(None, SomeBlob, gg, element, gg.get_namespace())
898                element = element[0]
899
900                print(gg.howlong)
901                print(etree.tostring(element, pretty_print=True))
902                assert element[0].text == answer
903
904                data = element.find('{%s}howlong' % gg.get_namespace()).text
905                self.assertEqual(data, answer)
906                s1 = XmlDocument().from_element(None, SomeBlob, element)
907                assert s1.howlong.total_seconds() == secs
908
909    def test_duration_positive_seconds_only(self):
910        answer = 'PT35S'
911        gg = SomeBlob()
912        gg.howlong = timedelta(seconds=35)
913
914        element = etree.Element('test')
915        XmlDocument().to_parent(None, SomeBlob, gg, element, gg.get_namespace())
916        element = element[0]
917
918        print(gg.howlong)
919        print(etree.tostring(element, pretty_print=True))
920        assert element[0].text == answer
921
922        data = element.find('{%s}howlong' % gg.get_namespace()).text
923        self.assertEqual(data, answer)
924        s1 = XmlDocument().from_element(None, SomeBlob, element)
925        assert s1.howlong.total_seconds() == gg.howlong.total_seconds()
926
927    def test_duration_positive_minutes_and_seconds_only(self):
928        answer = 'PT5M35S'
929        gg = SomeBlob()
930        gg.howlong = timedelta(minutes=5, seconds=35)
931
932        element = etree.Element('test')
933        XmlDocument().to_parent(None, SomeBlob, gg, element, gg.get_namespace())
934        element = element[0]
935
936        print(gg.howlong)
937        print(etree.tostring(element, pretty_print=True))
938        assert element[0].text == answer
939
940        data = element.find('{%s}howlong' % gg.get_namespace()).text
941        self.assertEqual(data, answer)
942        s1 = XmlDocument().from_element(None, SomeBlob, element)
943        assert s1.howlong.total_seconds() == gg.howlong.total_seconds()
944
945    def test_duration_positive_milliseconds_only(self):
946        answer = 'PT0.666000S'
947        gg = SomeBlob()
948        gg.howlong = timedelta(milliseconds=666)
949
950        element = etree.Element('test')
951        XmlDocument().to_parent(None, SomeBlob, gg, element, gg.get_namespace())
952        element = element[0]
953
954        print(gg.howlong)
955        print(etree.tostring(element, pretty_print=True))
956        assert element[0].text == answer
957
958        data = element.find('{%s}howlong' % gg.get_namespace()).text
959        self.assertEqual(data, answer)
960        s1 = XmlDocument().from_element(None, SomeBlob, element)
961        assert s1.howlong.total_seconds() == gg.howlong.total_seconds()
962
963    def test_duration_xml_duration(self):
964        dur = datetime.timedelta(days=5 + 30 + 365, hours=1, minutes=1,
965                                                   seconds=12, microseconds=8e5)
966
967        str1 = 'P400DT3672.8S'
968        str2 = 'P1Y1M5DT1H1M12.8S'
969
970        self.assertEqual(dur, ProtocolBase().from_unicode(Duration, str1))
971        self.assertEqual(dur, ProtocolBase().from_unicode(Duration, str2))
972
973        self.assertEqual(dur, ProtocolBase().from_unicode(Duration,
974                                      ProtocolBase().to_unicode(Duration, dur)))
975
976
977if __name__ == '__main__':
978    unittest.main()
979