source: trunk/tests/test_classic_functions.py @ 814

Last change on this file since 814 was 814, checked in by cito, 4 years ago

Add typecasting of dates, times, timestamps, intervals

So far, PyGreSQL has returned these types only as strings (in various
formats depending on the DateStyle? setting) and left it to the user
to parse and interpret the strings. These types are now properly cast
into the corresponding detetime types of Python, and this works with
any setting of DatesStyle?, even if you change DateStyle? in the middle
of a database session.

To implement this, a fast method for getting the datestyle (cached and
without roundtrip to the database) has been added. Also, the typecast
mechanism has been extended so that typecast functions can optionally
also take the connection as argument.

The date and time typecast functions have been implemented in Python
using the new typecast registry and added to both pg and pgdb. Some
duplication of code in the two modules was unavoidable, since we don't
want the modules to be dependent of each other or install additional
helper modules. One day we might want to change this, put everything
in one package and factor out some of the functionality.

  • Property svn:executable set to *
  • Property svn:keywords set to Id
File size: 38.5 KB
Line 
1#! /usr/bin/python
2# -*- coding: utf-8 -*-
3
4"""Test the classic PyGreSQL interface.
5
6Sub-tests for the module functions and constants.
7
8Contributed by Christoph Zwerschke.
9
10These tests do not need a database to test against.
11"""
12
13try:
14    import unittest2 as unittest  # for Python < 2.7
15except ImportError:
16    import unittest
17
18import json
19import re
20
21import pg  # the module under test
22
23from datetime import timedelta
24
25try:
26    long
27except NameError:  # Python >= 3.0
28    long = int
29
30try:
31    unicode
32except NameError:  # Python >= 3.0
33    unicode = str
34
35
36class TestHasConnect(unittest.TestCase):
37    """Test existence of basic pg module functions."""
38
39    def testhasPgError(self):
40        self.assertTrue(issubclass(pg.Error, Exception))
41
42    def testhasPgWarning(self):
43        self.assertTrue(issubclass(pg.Warning, Exception))
44
45    def testhasPgInterfaceError(self):
46        self.assertTrue(issubclass(pg.InterfaceError, pg.Error))
47
48    def testhasPgDatabaseError(self):
49        self.assertTrue(issubclass(pg.DatabaseError, pg.Error))
50
51    def testhasPgInternalError(self):
52        self.assertTrue(issubclass(pg.InternalError, pg.DatabaseError))
53
54    def testhasPgOperationalError(self):
55        self.assertTrue(issubclass(pg.OperationalError, pg.DatabaseError))
56
57    def testhasPgProgrammingError(self):
58        self.assertTrue(issubclass(pg.ProgrammingError, pg.DatabaseError))
59
60    def testhasPgIntegrityError(self):
61        self.assertTrue(issubclass(pg.IntegrityError, pg.DatabaseError))
62
63    def testhasPgDataError(self):
64        self.assertTrue(issubclass(pg.DataError, pg.DatabaseError))
65
66    def testhasPgNotSupportedError(self):
67        self.assertTrue(issubclass(pg.NotSupportedError, pg.DatabaseError))
68
69    def testhasConnect(self):
70        self.assertTrue(callable(pg.connect))
71
72    def testhasEscapeString(self):
73        self.assertTrue(callable(pg.escape_string))
74
75    def testhasEscapeBytea(self):
76        self.assertTrue(callable(pg.escape_bytea))
77
78    def testhasUnescapeBytea(self):
79        self.assertTrue(callable(pg.unescape_bytea))
80
81    def testDefHost(self):
82        d0 = pg.get_defhost()
83        d1 = 'pgtesthost'
84        pg.set_defhost(d1)
85        self.assertEqual(pg.get_defhost(), d1)
86        pg.set_defhost(d0)
87        self.assertEqual(pg.get_defhost(), d0)
88
89    def testDefPort(self):
90        d0 = pg.get_defport()
91        d1 = 1234
92        pg.set_defport(d1)
93        self.assertEqual(pg.get_defport(), d1)
94        if d0 is None:
95            d0 = -1
96        pg.set_defport(d0)
97        if d0 == -1:
98            d0 = None
99        self.assertEqual(pg.get_defport(), d0)
100
101    def testDefOpt(self):
102        d0 = pg.get_defopt()
103        d1 = '-h pgtesthost -p 1234'
104        pg.set_defopt(d1)
105        self.assertEqual(pg.get_defopt(), d1)
106        pg.set_defopt(d0)
107        self.assertEqual(pg.get_defopt(), d0)
108
109    def testDefBase(self):
110        d0 = pg.get_defbase()
111        d1 = 'pgtestdb'
112        pg.set_defbase(d1)
113        self.assertEqual(pg.get_defbase(), d1)
114        pg.set_defbase(d0)
115        self.assertEqual(pg.get_defbase(), d0)
116
117
118class TestParseArray(unittest.TestCase):
119    """Test the array parser."""
120
121    test_strings = [
122        ('', str, ValueError),
123        ('{}', None, []),
124        ('{}', str, []),
125        ('   {   }   ', None, []),
126        ('{', str, ValueError),
127        ('{{}', str, ValueError),
128        ('{}{', str, ValueError),
129        ('[]', str, ValueError),
130        ('()', str, ValueError),
131        ('{[]}', str, ['[]']),
132        ('{hello}', int, ValueError),
133        ('{42}', int, [42]),
134        ('{ 42 }', int, [42]),
135        ('{42', int, ValueError),
136        ('{ 42 ', int, ValueError),
137        ('{hello}', str, ['hello']),
138        ('{ hello }', str, ['hello']),
139        ('{hi}   ', str, ['hi']),
140        ('{hi}   ?', str, ValueError),
141        ('{null}', str, [None]),
142        (' { NULL } ', str, [None]),
143        ('   {   NULL   }   ', str, [None]),
144        (' { not null } ', str, ['not null']),
145        (' { not NULL } ', str, ['not NULL']),
146        (' {"null"} ', str, ['null']),
147        (' {"NULL"} ', str, ['NULL']),
148        ('{Hi!}', str, ['Hi!']),
149        ('{"Hi!"}', str, ['Hi!']),
150        ('{" Hi! "}', str, [' Hi! ']),
151        ('{a"}', str, ValueError),
152        ('{"b}', str, ValueError),
153        ('{a"b}', str, ValueError),
154        (r'{a\"b}', str, ['a"b']),
155        (r'{a\,b}', str, ['a,b']),
156        (r'{a\bc}', str, ['abc']),
157        (r'{"a\bc"}', str, ['abc']),
158        (r'{\a\b\c}', str, ['abc']),
159        (r'{"\a\b\c"}', str, ['abc']),
160        (r'{"a"b"}', str, ValueError),
161        (r'{"a""b"}', str, ValueError),
162        (r'{"a\"b"}', str, ['a"b']),
163        ('{"{}"}', str, ['{}']),
164        (r'{\{\}}', str, ['{}']),
165        ('{"{a,b,c}"}', str, ['{a,b,c}']),
166        ("{'abc'}", str, ["'abc'"]),
167        ('{"abc"}', str, ['abc']),
168        (r'{\"abc\"}', str, ['"abc"']),
169        (r"{\'abc\'}", str, ["'abc'"]),
170        (r"{abc,d,efg}", str, ['abc', 'd', 'efg']),
171        ('{Hello World!}', str, ['Hello World!']),
172        ('{Hello, World!}', str, ['Hello', 'World!']),
173        ('{Hello,\ World!}', str, ['Hello', ' World!']),
174        ('{Hello\, World!}', str, ['Hello, World!']),
175        ('{"Hello World!"}', str, ['Hello World!']),
176        ('{this, should, be, null}', str, ['this', 'should', 'be', None]),
177        ('{This, should, be, NULL}', str, ['This', 'should', 'be', None]),
178        ('{3, 2, 1, null}', int, [3, 2, 1, None]),
179        ('{3, 2, 1, NULL}', int, [3, 2, 1, None]),
180        ('{3,17,51}', int, [3, 17, 51]),
181        (' { 3 , 17 , 51 } ', int, [3, 17, 51]),
182        ('{3,17,51}', str, ['3', '17', '51']),
183        (' { 3 , 17 , 51 } ', str, ['3', '17', '51']),
184        ('{1,"2",abc,"def"}', str, ['1', '2', 'abc', 'def']),
185        ('{{}}', int, [[]]),
186        ('{{},{}}', int, [[], []]),
187        ('{ {} , {} , {} }', int, [[], [], []]),
188        ('{ {} , {} , {} , }', int, ValueError),
189        ('{{{1,2,3},{4,5,6}}}', int, [[[1, 2, 3], [4, 5, 6]]]),
190        ('{{1,2,3},{4,5,6},{7,8,9}}', int, [[1, 2, 3], [4, 5, 6], [7, 8, 9]]),
191        ('{20000, 25000, 25000, 25000}', int, [20000, 25000, 25000, 25000]),
192        ('{{{17,18,19},{14,15,16},{11,12,13}},'
193         '{{27,28,29},{24,25,26},{21,22,23}},'
194         '{{37,38,39},{34,35,36},{31,32,33}}}', int,
195            [[[17, 18, 19], [14, 15, 16], [11, 12, 13]],
196             [[27, 28, 29], [24, 25, 26], [21, 22, 23]],
197             [[37, 38, 39], [34, 35, 36], [31, 32, 33]]]),
198        ('{{"breakfast", "consulting"}, {"meeting", "lunch"}}', str,
199            [['breakfast', 'consulting'], ['meeting', 'lunch']]),
200        ('[1:3]={1,2,3}', int, [1, 2, 3]),
201        ('[-1:1]={1,2,3}', int, [1, 2, 3]),
202        ('[-1:+1]={1,2,3}', int, [1, 2, 3]),
203        ('[-3:-1]={1,2,3}', int, [1, 2, 3]),
204        ('[+1:+3]={1,2,3}', int, [1, 2, 3]),
205        ('[]={1,2,3}', int, ValueError),
206        ('[1:]={1,2,3}', int, ValueError),
207        ('[:3]={1,2,3}', int, ValueError),
208        ('[1:1][-2:-1][3:5]={{{1,2,3},{4,5,6}}}',
209            int, [[[1, 2, 3], [4, 5, 6]]]),
210        ('  [1:1]  [-2:-1]  [3:5]  =  { { { 1 , 2 , 3 }, {4 , 5 , 6 } } }',
211            int, [[[1, 2, 3], [4, 5, 6]]]),
212        ('[1:1][3:5]={{1,2,3},{4,5,6}}', int, [[1, 2, 3], [4, 5, 6]]),
213        ('[3:5]={{1,2,3},{4,5,6}}', int, ValueError),
214        ('[1:1][-2:-1][3:5]={{1,2,3},{4,5,6}}', int, ValueError)]
215
216    def testParserParams(self):
217        f = pg.cast_array
218        self.assertRaises(TypeError, f)
219        self.assertRaises(TypeError, f, None)
220        self.assertRaises(TypeError, f, '{}', 1)
221        self.assertRaises(TypeError, f, '{}', b',',)
222        self.assertRaises(TypeError, f, '{}', None, None)
223        self.assertRaises(TypeError, f, '{}', None, 1)
224        self.assertRaises(TypeError, f, '{}', None, b'')
225        self.assertRaises(ValueError, f, '{}', None, b'\\')
226        self.assertRaises(ValueError, f, '{}', None, b'{')
227        self.assertRaises(ValueError, f, '{}', None, b'}')
228        self.assertRaises(TypeError, f, '{}', None, b',;')
229        self.assertEqual(f('{}'), [])
230        self.assertEqual(f('{}', None), [])
231        self.assertEqual(f('{}', None, b';'), [])
232        self.assertEqual(f('{}', str), [])
233        self.assertEqual(f('{}', str, b';'), [])
234
235    def testParserSimple(self):
236        r = pg.cast_array('{a,b,c}')
237        self.assertIsInstance(r, list)
238        self.assertEqual(len(r), 3)
239        self.assertEqual(r, ['a', 'b', 'c'])
240
241    def testParserNested(self):
242        f = pg.cast_array
243        r = f('{{a,b,c}}')
244        self.assertIsInstance(r, list)
245        self.assertEqual(len(r), 1)
246        r = r[0]
247        self.assertIsInstance(r, list)
248        self.assertEqual(len(r), 3)
249        self.assertEqual(r, ['a', 'b', 'c'])
250        self.assertRaises(ValueError, f, '{a,{b,c}}')
251        r = f('{{a,b},{c,d}}')
252        self.assertIsInstance(r, list)
253        self.assertEqual(len(r), 2)
254        r = r[1]
255        self.assertIsInstance(r, list)
256        self.assertEqual(len(r), 2)
257        self.assertEqual(r, ['c', 'd'])
258        r = f('{{a},{b},{c}}')
259        self.assertIsInstance(r, list)
260        self.assertEqual(len(r), 3)
261        r = r[1]
262        self.assertIsInstance(r, list)
263        self.assertEqual(len(r), 1)
264        self.assertEqual(r[0], 'b')
265        r = f('{{{{{{{abc}}}}}}}')
266        for i in range(7):
267            self.assertIsInstance(r, list)
268            self.assertEqual(len(r), 1)
269            r = r[0]
270        self.assertEqual(r, 'abc')
271
272    def testParserTooDeeplyNested(self):
273        f = pg.cast_array
274        for n in 3, 5, 9, 12, 16, 32, 64, 256:
275            r = '%sa,b,c%s' % ('{' * n, '}' * n)
276            if n > 16:  # hard coded maximum depth
277                self.assertRaises(ValueError, f, r)
278            else:
279                r = f(r)
280                for i in range(n - 1):
281                    self.assertIsInstance(r, list)
282                    self.assertEqual(len(r), 1)
283                    r = r[0]
284                self.assertEqual(len(r), 3)
285                self.assertEqual(r, ['a', 'b', 'c'])
286
287    def testParserCast(self):
288        f = pg.cast_array
289        self.assertEqual(f('{1}'), ['1'])
290        self.assertEqual(f('{1}', None), ['1'])
291        self.assertEqual(f('{1}', int), [1])
292        self.assertEqual(f('{1}', str), ['1'])
293        self.assertEqual(f('{a}'), ['a'])
294        self.assertEqual(f('{a}', None), ['a'])
295        self.assertRaises(ValueError, f, '{a}', int)
296        self.assertEqual(f('{a}', str), ['a'])
297        cast = lambda s: '%s is ok' % s
298        self.assertEqual(f('{a}', cast), ['a is ok'])
299
300    def testParserDelim(self):
301        f = pg.cast_array
302        self.assertEqual(f('{1,2}'), ['1', '2'])
303        self.assertEqual(f('{1,2}', delim=b','), ['1', '2'])
304        self.assertEqual(f('{1;2}'), ['1;2'])
305        self.assertEqual(f('{1;2}', delim=b';'), ['1', '2'])
306        self.assertEqual(f('{1,2}', delim=b';'), ['1,2'])
307
308    def testParserWithData(self):
309        f = pg.cast_array
310        for string, cast, expected in self.test_strings:
311            if expected is ValueError:
312                self.assertRaises(ValueError, f, string, cast)
313            else:
314                self.assertEqual(f(string, cast), expected)
315
316    def testParserWithoutCast(self):
317        f = pg.cast_array
318
319        for string, cast, expected in self.test_strings:
320            if cast is not str:
321                continue
322            if expected is ValueError:
323                self.assertRaises(ValueError, f, string)
324            else:
325                self.assertEqual(f(string), expected)
326
327    def testParserWithDifferentDelimiter(self):
328        f = pg.cast_array
329
330        def replace_comma(value):
331            if isinstance(value, str):
332                return value.replace(',', ';')
333            elif isinstance(value, list):
334                return [replace_comma(v) for v in value]
335            else:
336                return value
337
338        for string, cast, expected in self.test_strings:
339            string = replace_comma(string)
340            if expected is ValueError:
341                self.assertRaises(ValueError, f, string, cast)
342            else:
343                expected = replace_comma(expected)
344                self.assertEqual(f(string, cast, b';'), expected)
345
346
347class TestParseRecord(unittest.TestCase):
348    """Test the record parser."""
349
350    test_strings = [
351        ('', None, ValueError),
352        ('', str, ValueError),
353        ('(', None, ValueError),
354        ('(', str, ValueError),
355        ('()', None, (None,)),
356        ('()', str, (None,)),
357        ('()', int, (None,)),
358        ('(,)', str, (None, None)),
359        ('( , )', str, (' ', ' ')),
360        ('(")', None, ValueError),
361        ('("")', None, ('',)),
362        ('("")', str, ('',)),
363        ('("")', int, ValueError),
364        ('("" )', None, (' ',)),
365        ('("" )', str, (' ',)),
366        ('("" )', int, ValueError),
367        ('    ()    ', None, (None,)),
368        ('   (   )   ', None, ('   ',)),
369        ('(', str, ValueError),
370        ('(()', str, ('(',)),
371        ('(())', str, ValueError),
372        ('()(', str, ValueError),
373        ('()()', str, ValueError),
374        ('[]', str, ValueError),
375        ('{}', str, ValueError),
376        ('([])', str, ('[]',)),
377        ('(hello)', int, ValueError),
378        ('(42)', int, (42,)),
379        ('( 42 )', int, (42,)),
380        ('(  42)', int, (42,)),
381        ('(42)', str, ('42',)),
382        ('( 42 )', str, (' 42 ',)),
383        ('(  42)', str, ('  42',)),
384        ('(42', int, ValueError),
385        ('( 42 ', int, ValueError),
386        ('(hello)', str, ('hello',)),
387        ('( hello )', str, (' hello ',)),
388        ('(hello))', str, ValueError),
389        ('   (hello)   ', str, ('hello',)),
390        ('   (hello)   )', str, ValueError),
391        ('(hello)?', str, ValueError),
392        ('(null)', str, ('null',)),
393        ('(null)', int, ValueError),
394        (' ( NULL ) ', str, (' NULL ',)),
395        ('   (   NULL   )   ', str, ('   NULL   ',)),
396        (' ( null null ) ', str, (' null null ',)),
397        (' ("null") ', str, ('null',)),
398        (' ("NULL") ', str, ('NULL',)),
399        ('(Hi!)', str, ('Hi!',)),
400        ('("Hi!")', str, ('Hi!',)),
401        ("('Hi!')", str, ("'Hi!'",)),
402        ('(" Hi! ")', str, (' Hi! ',)),
403        ('("Hi!" )', str, ('Hi! ',)),
404        ('( "Hi!")', str, (' Hi!',)),
405        ('( "Hi!" )', str, (' Hi! ',)),
406        ('( ""Hi!"" )', str, (' Hi! ',)),
407        ('( """Hi!""" )', str, (' "Hi!" ',)),
408        ('(a")', str, ValueError),
409        ('("b)', str, ValueError),
410        ('("a" "b)', str, ValueError),
411        ('("a" "b")', str, ('a b',)),
412        ('( "a" "b" "c" )', str, (' a b c ',)),
413        ('(  "a"  "b"  "c"  )', str, ('  a  b  c  ',)),
414        ('(  "a,b"  "c,d"  )', str, ('  a,b  c,d  ',)),
415        ('( "(a,b,c)" d, e, "f,g")', str, (' (a,b,c) d', ' e', ' f,g')),
416        ('(a",b,c",d,"e,f")', str, ('a,b,c', 'd', 'e,f')),
417        ('( """a,b""", ""c,d"", "e,f", "g", ""h"", """i""")', str,
418            (' "a,b"', ' c', 'd', ' e,f', ' g', ' h', ' "i"')),
419        ('(a",b)",c"),(d,e)",f,g)', str, ('a,b)', 'c),(d,e)', 'f', 'g')),
420        ('(a"b)', str, ValueError),
421        (r'(a\"b)', str, ('a"b',)),
422        ('(a""b)', str, ('ab',)),
423        ('("a""b")', str, ('a"b',)),
424        (r'(a\,b)', str, ('a,b',)),
425        (r'(a\bc)', str, ('abc',)),
426        (r'("a\bc")', str, ('abc',)),
427        (r'(\a\b\c)', str, ('abc',)),
428        (r'("\a\b\c")', str, ('abc',)),
429        ('("()")', str, ('()',)),
430        (r'(\,)', str, (',',)),
431        (r'(\(\))', str, ('()',)),
432        (r'(\)\()', str, (')(',)),
433        ('("(a,b,c)")', str, ('(a,b,c)',)),
434        ("('abc')", str, ("'abc'",)),
435        ('("abc")', str, ('abc',)),
436        (r'(\"abc\")', str, ('"abc"',)),
437        (r"(\'abc\')", str, ("'abc'",)),
438        ('(Hello World!)', str, ('Hello World!',)),
439        ('(Hello, World!)', str, ('Hello', ' World!',)),
440        ('(Hello,\ World!)', str, ('Hello', ' World!',)),
441        ('(Hello\, World!)', str, ('Hello, World!',)),
442        ('("Hello World!")', str, ('Hello World!',)),
443        ("(this,shouldn't,be,null)", str, ('this', "shouldn't", 'be', 'null')),
444        ('(null,should,be,)', str, ('null', 'should', 'be', None)),
445        ('(abcABC0123!?+-*/=&%$\\\\\'\\"{[]}"""":;\\,,)', str,
446            ('abcABC0123!?+-*/=&%$\\\'"{[]}":;,', None)),
447        ('(3, 2, 1,)', int, (3, 2, 1, None)),
448        ('(3, 2, 1, )', int, ValueError),
449        ('(, 1, 2, 3)', int, (None, 1, 2, 3)),
450        ('( , 1, 2, 3)', int, ValueError),
451        ('(,1,,2,,3,)', int, (None, 1, None, 2, None, 3, None)),
452        ('(3,17,51)', int, (3, 17, 51)),
453        (' ( 3 , 17 , 51 ) ', int, (3, 17, 51)),
454        ('(3,17,51)', str, ('3', '17', '51')),
455        (' ( 3 , 17 , 51 ) ', str, (' 3 ', ' 17 ', ' 51 ')),
456        ('(1,"2",abc,"def")', str, ('1', '2', 'abc', 'def')),
457        ('(())', str, ValueError),
458        ('()))', str, ValueError),
459        ('()()', str, ValueError),
460        ('((()', str, ('((',)),
461        ('(())', int, ValueError),
462        ('((),())', str, ValueError),
463        ('("()","()")', str, ('()', '()')),
464        ('( " () , () , () " )', str, ('  () , () , ()  ',)),
465        ('(20000, 25000, 25000, 25000)', int, (20000, 25000, 25000, 25000)),
466        ('("breakfast","consulting","meeting","lunch")', str,
467            ('breakfast', 'consulting', 'meeting', 'lunch')),
468        ('("breakfast","consulting","meeting","lunch")',
469            (str, str, str), ValueError),
470        ('("breakfast","consulting","meeting","lunch")', (str, str, str, str),
471            ('breakfast', 'consulting', 'meeting', 'lunch')),
472        ('("breakfast","consulting","meeting","lunch")',
473            (str, str, str, str, str), ValueError),
474        ('("fuzzy dice",42,1.9375)', None, ('fuzzy dice', '42', '1.9375')),
475        ('("fuzzy dice",42,1.9375)', str, ('fuzzy dice', '42', '1.9375')),
476        ('("fuzzy dice",42,1.9375)', int, ValueError),
477        ('("fuzzy dice",42,1.9375)', (str, int, float),
478            ('fuzzy dice', 42, 1.9375)),
479        ('("fuzzy dice",42,1.9375)', (str, int), ValueError),
480        ('("fuzzy dice",42,1.9375)', (str, int, float, str), ValueError),
481        ('("fuzzy dice",42,)', (str, int, float), ('fuzzy dice', 42, None)),
482        ('("fuzzy dice",42,)', (str, int), ValueError),
483        ('("",42,)', (str, int, float), ('', 42, None)),
484        ('("fuzzy dice","",1.9375)', (str, int, float), ValueError),
485        ('(fuzzy dice,"42","1.9375")', (str, int, float),
486            ('fuzzy dice', 42, 1.9375))]
487
488    def testParserParams(self):
489        f = pg.cast_record
490        self.assertRaises(TypeError, f)
491        self.assertRaises(TypeError, f, None)
492        self.assertRaises(TypeError, f, '()', 1)
493        self.assertRaises(TypeError, f, '()', b',',)
494        self.assertRaises(TypeError, f, '()', None, None)
495        self.assertRaises(TypeError, f, '()', None, 1)
496        self.assertRaises(TypeError, f, '()', None, b'')
497        self.assertRaises(ValueError, f, '()', None, b'\\')
498        self.assertRaises(ValueError, f, '()', None, b'(')
499        self.assertRaises(ValueError, f, '()', None, b')')
500        self.assertRaises(TypeError, f, '{}', None, b',;')
501        self.assertEqual(f('()'), (None,))
502        self.assertEqual(f('()', None), (None,))
503        self.assertEqual(f('()', None, b';'), (None,))
504        self.assertEqual(f('()', str), (None,))
505        self.assertEqual(f('()', str, b';'), (None,))
506
507    def testParserSimple(self):
508        r = pg.cast_record('(a,b,c)')
509        self.assertIsInstance(r, tuple)
510        self.assertEqual(len(r), 3)
511        self.assertEqual(r, ('a', 'b', 'c'))
512
513    def testParserNested(self):
514        f = pg.cast_record
515        self.assertRaises(ValueError, f, '((a,b,c))')
516        self.assertRaises(ValueError, f, '((a,b),(c,d))')
517        self.assertRaises(ValueError, f, '((a),(b),(c))')
518        self.assertRaises(ValueError, f, '(((((((abc)))))))')
519
520    def testParserManyElements(self):
521        f = pg.cast_record
522        for n in 3, 5, 9, 12, 16, 32, 64, 256:
523            r = '(%s)' % ','.join(map(str, range(n)))
524            r = f(r, int)
525            self.assertEqual(r, tuple(range(n)))
526
527    def testParserCastUniform(self):
528        f = pg.cast_record
529        self.assertEqual(f('(1)'), ('1',))
530        self.assertEqual(f('(1)', None), ('1',))
531        self.assertEqual(f('(1)', int), (1,))
532        self.assertEqual(f('(1)', str), ('1',))
533        self.assertEqual(f('(a)'), ('a',))
534        self.assertEqual(f('(a)', None), ('a',))
535        self.assertRaises(ValueError, f, '(a)', int)
536        self.assertEqual(f('(a)', str), ('a',))
537        cast = lambda s: '%s is ok' % s
538        self.assertEqual(f('(a)', cast), ('a is ok',))
539
540    def testParserCastNonUniform(self):
541        f = pg.cast_record
542        self.assertEqual(f('(1)', []), ('1',))
543        self.assertEqual(f('(1)', [None]), ('1',))
544        self.assertEqual(f('(1)', [str]), ('1',))
545        self.assertEqual(f('(1)', [int]), (1,))
546        self.assertRaises(ValueError, f, '(1)', [None, None])
547        self.assertRaises(ValueError, f, '(1)', [str, str])
548        self.assertRaises(ValueError, f, '(1)', [int, int])
549        self.assertEqual(f('(a)', [None]), ('a',))
550        self.assertEqual(f('(a)', [str]), ('a',))
551        self.assertRaises(ValueError, f, '(a)', [int])
552        self.assertEqual(f('(1,a)', [int, str]), (1, 'a'))
553        self.assertRaises(ValueError, f, '(1,a)', [str, int])
554        self.assertEqual(f('(a,1)', [str, int]), ('a', 1))
555        self.assertRaises(ValueError, f, '(a,1)', [int, str])
556        self.assertEqual(f('(1,a,2,b,3,c)',
557            [int, str, int, str, int, str]), (1, 'a', 2, 'b', 3, 'c'))
558        self.assertEqual(f('(1,a,2,b,3,c)',
559            (int, str, int, str, int, str)), (1, 'a', 2, 'b', 3, 'c'))
560        cast1 = lambda s: '%s is ok' % s
561        self.assertEqual(f('(a)', [cast1]), ('a is ok',))
562        cast2 = lambda s: 'and %s is ok, too' % s
563        self.assertEqual(f('(a,b)', [cast1, cast2]),
564            ('a is ok', 'and b is ok, too'))
565        self.assertRaises(ValueError, f, '(a)', [cast1, cast2])
566        self.assertRaises(ValueError, f, '(a,b,c)', [cast1, cast2])
567        self.assertEqual(f('(1,2,3,4,5,6)',
568            [int, float, str, None, cast1, cast2]),
569            (1, 2.0, '3', '4', '5 is ok', 'and 6 is ok, too'))
570
571    def testParserDelim(self):
572        f = pg.cast_record
573        self.assertEqual(f('(1,2)'), ('1', '2'))
574        self.assertEqual(f('(1,2)', delim=b','), ('1', '2'))
575        self.assertEqual(f('(1;2)'), ('1;2',))
576        self.assertEqual(f('(1;2)', delim=b';'), ('1', '2'))
577        self.assertEqual(f('(1,2)', delim=b';'), ('1,2',))
578
579    def testParserWithData(self):
580        f = pg.cast_record
581        for string, cast, expected in self.test_strings:
582            if expected is ValueError:
583                self.assertRaises(ValueError, f, string, cast)
584            else:
585                self.assertEqual(f(string, cast), expected)
586
587    def testParserWithoutCast(self):
588        f = pg.cast_record
589
590        for string, cast, expected in self.test_strings:
591            if cast is not str:
592                continue
593            if expected is ValueError:
594                self.assertRaises(ValueError, f, string)
595            else:
596                self.assertEqual(f(string), expected)
597
598    def testParserWithDifferentDelimiter(self):
599        f = pg.cast_record
600
601        def replace_comma(value):
602            if isinstance(value, str):
603                return value.replace(';', '@').replace(
604                    ',', ';').replace('@', ',')
605            elif isinstance(value, tuple):
606                return tuple(replace_comma(v) for v in value)
607            else:
608                return value
609
610        for string, cast, expected in self.test_strings:
611            string = replace_comma(string)
612            if expected is ValueError:
613                self.assertRaises(ValueError, f, string, cast)
614            else:
615                expected = replace_comma(expected)
616                self.assertEqual(f(string, cast, b';'), expected)
617
618
619class TestCastInterval(unittest.TestCase):
620    """Test the interval typecast function."""
621
622    intervals = [
623        ((0, 0, 0, 1, 0, 0, 0),
624            ('1:00:00', '01:00:00', '@ 1 hour', 'PT1H')),
625        ((0, 0, 0, -1, 0, 0, 0),
626            ('-1:00:00', '-01:00:00', '@ -1 hour', 'PT-1H')),
627        ((0, 0, 0, 1, 0, 0, 0),
628            ('0-0 0 1:00:00', '0 years 0 mons 0 days 01:00:00',
629            '@ 0 years 0 mons 0 days 1 hour', 'P0Y0M0DT1H')),
630        ((0, 0, 0, -1, 0, 0, 0),
631            ('-0-0 -1:00:00', '0 years 0 mons 0 days -01:00:00',
632            '@ 0 years 0 mons 0 days -1 hour', 'P0Y0M0DT-1H')),
633        ((0, 0, 1, 0, 0, 0, 0),
634            ('1 0:00:00', '1 day', '@ 1 day', 'P1D')),
635        ((0, 0, -1, 0, 0, 0, 0),
636            ('-1 0:00:00', '-1 day', '@ -1 day', 'P-1D')),
637        ((0, 1, 0, 0, 0, 0, 0),
638            ('0-1', '1 mon', '@ 1 mon', 'P1M')),
639        ((1, 0, 0, 0, 0, 0, 0),
640            ('1-0', '1 year', '@ 1 year', 'P1Y')),
641        ((0, 0, 0, 2, 0, 0, 0),
642            ('2:00:00', '02:00:00', '@ 2 hours', 'PT2H')),
643        ((0, 0, 2, 0, 0, 0, 0),
644            ('2 0:00:00', '2 days', '@ 2 days', 'P2D')),
645        ((0, 2, 0, 0, 0, 0, 0),
646            ('0-2', '2 mons', '@ 2 mons', 'P2M')),
647        ((2, 0, 0, 0, 0, 0, 0),
648            ('2-0', '2 years', '@ 2 years', 'P2Y')),
649        ((0, 0, 0, -3, 0, 0, 0),
650            ('-3:00:00', '-03:00:00', '@ 3 hours ago', 'PT-3H')),
651        ((0, 0, -3, 0, 0, 0, 0),
652            ('-3 0:00:00', '-3 days', '@ 3 days ago', 'P-3D')),
653        ((0, -3, 0, 0, 0, 0, 0),
654            ('-0-3', '-3 mons', '@ 3 mons ago', 'P-3M')),
655        ((-3, 0, 0, 0, 0, 0, 0),
656            ('-3-0', '-3 years', '@ 3 years ago', 'P-3Y')),
657        ((0, 0, 0, 0, 1, 0, 0),
658            ('0:01:00', '00:01:00', '@ 1 min', 'PT1M')),
659        ((0, 0, 0, 0, 0, 1, 0),
660            ('0:00:01', '00:00:01', '@ 1 sec', 'PT1S')),
661        ((0, 0, 0, 0, 0, 0, 1),
662            ('0:00:00.000001', '00:00:00.000001',
663             '@ 0.000001 secs', 'PT0.000001S')),
664        ((0, 0, 0, 0, 2, 0, 0),
665            ('0:02:00', '00:02:00', '@ 2 mins', 'PT2M')),
666        ((0, 0, 0, 0, 0, 2, 0),
667            ('0:00:02', '00:00:02', '@ 2 secs', 'PT2S')),
668        ((0, 0, 0, 0, 0, 0, 2),
669            ('0:00:00.000002', '00:00:00.000002',
670             '@ 0.000002 secs', 'PT0.000002S')),
671        ((0, 0, 0, 0, -3, 0, 0),
672            ('-0:03:00', '-00:03:00', '@ 3 mins ago', 'PT-3M')),
673        ((0, 0, 0, 0, 0, -3, 0),
674            ('-0:00:03', '-00:00:03', '@ 3 secs ago', 'PT-3S')),
675        ((0, 0, 0, 0, 0, 0, -3),
676            ('-0:00:00.000003', '-00:00:00.000003',
677             '@ 0.000003 secs ago', 'PT-0.000003S')),
678        ((1, 2, 0, 0, 0, 0, 0),
679            ('1-2', '1 year 2 mons', '@ 1 year 2 mons', 'P1Y2M')),
680        ((0, 0, 3, 4, 5, 6, 0),
681            ('3 4:05:06', '3 days 04:05:06',
682             '@ 3 days 4 hours 5 mins 6 secs', 'P3DT4H5M6S')),
683        ((1, 2, 3, 4, 5, 6, 0),
684            ('+1-2 +3 +4:05:06', '1 year 2 mons 3 days 04:05:06',
685             '@ 1 year 2 mons 3 days 4 hours 5 mins 6 secs',
686             'P1Y2M3DT4H5M6S')),
687        ((1, 2, 3, -4, -5, -6, 0),
688            ('+1-2 +3 -4:05:06', '1 year 2 mons 3 days -04:05:06',
689             '@ 1 year 2 mons 3 days -4 hours -5 mins -6 secs',
690             'P1Y2M3DT-4H-5M-6S')),
691        ((1, 2, 3, -4, 5, 6, 0),
692            ('+1-2 +3 -3:54:54', '1 year 2 mons 3 days -03:54:54',
693             '@ 1 year 2 mons 3 days -3 hours -54 mins -54 secs',
694             'P1Y2M3DT-3H-54M-54S')),
695        ((-1, -2, 3, -4, -5, -6, 0),
696            ('-1-2 +3 -4:05:06', '-1 years -2 mons +3 days -04:05:06',
697             '@ 1 year 2 mons -3 days 4 hours 5 mins 6 secs ago',
698             'P-1Y-2M3DT-4H-5M-6S')),
699        ((1, 2, -3, 4, 5, 6, 0),
700            ('+1-2 -3 +4:05:06', '1 year 2 mons -3 days +04:05:06',
701             '@ 1 year 2 mons -3 days 4 hours 5 mins 6 secs',
702             'P1Y2M-3DT4H5M6S')),
703        ((0, 0, 0, 1, 30, 0, 0),
704            ('1:30:00', '01:30:00', '@ 1 hour 30 mins', 'PT1H30M')),
705        ((0, 0, 0, 3, 15, 45, 123456),
706            ('3:15:45.123456', '03:15:45.123456',
707             '@ 3 hours 15 mins 45.123456 secs', 'PT3H15M45.123456S')),
708        ((0, 0, 0, 3, 15, -5, 123),
709            ('3:14:55.000123', '03:14:55.000123',
710             '@ 3 hours 14 mins 55.000123 secs', 'PT3H14M55.000123S')),
711        ((0, 0, 0, 3, -5, 15, -12345),
712            ('2:55:14.987655', '02:55:14.987655',
713             '@ 2 hours 55 mins 14.987655 secs', 'PT2H55M14.987655S')),
714        ((0, 0, 0, 2, -1, 0, 0),
715            ('1:59:00', '01:59:00', '@ 1 hour 59 mins', 'PT1H59M')),
716        ((0, 0, 0, -1, 2, 0, 0),
717            ('-0:58:00', '-00:58:00', '@ 58 mins ago', 'PT-58M')),
718        ((1, 11, 0, 0, 0, 0, 0),
719            ('1-11', '1 year 11 mons', '@ 1 year 11 mons', 'P1Y11M')),
720        ((0, -10, 0, 0, 0, 0, 0),
721            ('-0-10', '-10 mons', '@ 10 mons ago', 'P-10M')),
722        ((0, 0, 2, -1, 0, 0, 0),
723            ('+0-0 +2 -1:00:00', '2 days -01:00:00',
724             '@ 2 days -1 hours', 'P2DT-1H')),
725        ((0, 0, -1, 2, 0, 0, 0),
726            ('+0-0 -1 +2:00:00', '-1 days +02:00:00',
727             '@ 1 day -2 hours ago', 'P-1DT2H')),
728        ((0, 0, 1, 0, 0, 0, 1),
729            ('1 0:00:00.000001', '1 day 00:00:00.000001',
730             '@ 1 day 0.000001 secs', 'P1DT0.000001S')),
731        ((0, 0, 1, 0, 0, 1, 0),
732            ('1 0:00:01', '1 day 00:00:01', '@ 1 day 1 sec', 'P1DT1S')),
733        ((0, 0, 1, 0, 1, 0, 0),
734            ('1 0:01:00', '1 day 00:01:00', '@ 1 day 1 min', 'P1DT1M')),
735        ((0, 0, 0, 0, 1, 0, -1),
736            ('0:00:59.999999', '00:00:59.999999',
737             '@ 59.999999 secs', 'PT59.999999S')),
738        ((0, 0, 0, 0, -1, 0, 1),
739            ('-0:00:59.999999', '-00:00:59.999999',
740             '@ 59.999999 secs ago', 'PT-59.999999S')),
741        ((0, 0, 0, 0, -1, 1, 1),
742            ('-0:00:58.999999', '-00:00:58.999999',
743             '@ 58.999999 secs ago', 'PT-58.999999S')),
744        ((0, 0, 42, 0, 0, 0, 0),
745            ('42 0:00:00', '42 days', '@ 42 days', 'P42D')),
746        ((0, 0, -7, 0, 0, 0, 0),
747            ('-7 0:00:00', '-7 days', '@ 7 days ago', 'P-7D')),
748        ((1, 1, 1, 1, 1, 0, 0),
749            ('+1-1 +1 +1:01:00', '1 year 1 mon 1 day 01:01:00',
750             '@ 1 year 1 mon 1 day 1 hour 1 min', 'P1Y1M1DT1H1M')),
751        ((0, -11, -1, -1, 1, 0, 0),
752            ('-0-11 -1 -0:59:00', '-11 mons -1 days -00:59:00',
753             '@ 11 mons 1 day 59 mins ago', 'P-11M-1DT-59M')),
754        ((-1, -1, -1, -1, -1, 0, 0),
755            ('-1-1 -1 -1:01:00', '-1 years -1 mons -1 days -01:01:00',
756             '@ 1 year 1 mon 1 day 1 hour 1 min ago', 'P-1Y-1M-1DT-1H-1M')),
757        ((-1, 0, -3, 1, 0, 0, 0),
758            ('-1-0 -3 +1:00:00', '-1 years -3 days +01:00:00',
759             '@ 1 year 3 days -1 hours ago', 'P-1Y-3DT1H')),
760        ((1, 0, 0, 0, 0, 0, 1),
761            ('+1-0 +0 +0:00:00.000001', '1 year 00:00:00.000001',
762             '@ 1 year 0.000001 secs', 'P1YT0.000001S')),
763        ((1, 0, 0, 0, 0, 0, -1),
764            ('+1-0 +0 -0:00:00.000001', '1 year -00:00:00.000001',
765             '@ 1 year -0.000001 secs', 'P1YT-0.000001S')),
766        ((1, 2, 3, 4, 5, 6, 7),
767            ('+1-2 +3 +4:05:06.000007',
768             '1 year 2 mons 3 days 04:05:06.000007',
769             '@ 1 year 2 mons 3 days 4 hours 5 mins 6.000007 secs',
770             'P1Y2M3DT4H5M6.000007S')),
771        ((0, 10, 3, -4, 5, -6, 7),
772            ('+0-10 +3 -3:55:05.999993', '10 mons 3 days -03:55:05.999993',
773             '@ 10 mons 3 days -3 hours -55 mins -5.999993 secs',
774             'P10M3DT-3H-55M-5.999993S')),
775        ((0, -10, -3, 4, -5, 6, -7),
776            ('-0-10 -3 +3:55:05.999993',
777             '-10 mons -3 days +03:55:05.999993',
778             '@ 10 mons 3 days -3 hours -55 mins -5.999993 secs ago',
779             'P-10M-3DT3H55M5.999993S'))]
780
781    def testCastInterval(self):
782        for result, values in self.intervals:
783            f = pg.cast_interval
784            years, mons, days, hours, mins, secs, usecs = result
785            days += 365 * years + 30 * mons
786            interval = timedelta(days=days, hours=hours, minutes=mins,
787                seconds=secs, microseconds=usecs)
788            for value in values:
789                self.assertEqual(f(value), interval)
790
791
792class TestEscapeFunctions(unittest.TestCase):
793    """Test pg escape and unescape functions.
794
795    The libpq interface memorizes some parameters of the last opened
796    connection that influence the result of these functions.
797    Therefore we cannot do rigid tests of these functions here.
798    We leave this for the test module that runs with a database.
799
800    """
801
802    def testEscapeString(self):
803        f = pg.escape_string
804        r = f(b'plain')
805        self.assertIsInstance(r, bytes)
806        self.assertEqual(r, b'plain')
807        r = f(u'plain')
808        self.assertIsInstance(r, unicode)
809        self.assertEqual(r, u'plain')
810        r = f("that's cheese")
811        self.assertIsInstance(r, str)
812        self.assertEqual(r, "that''s cheese")
813
814    def testEscapeBytea(self):
815        f = pg.escape_bytea
816        r = f(b'plain')
817        self.assertIsInstance(r, bytes)
818        self.assertEqual(r, b'plain')
819        r = f(u'plain')
820        self.assertIsInstance(r, unicode)
821        self.assertEqual(r, u'plain')
822        r = f("that's cheese")
823        self.assertIsInstance(r, str)
824        self.assertEqual(r, "that''s cheese")
825
826    def testUnescapeBytea(self):
827        f = pg.unescape_bytea
828        r = f(b'plain')
829        self.assertIsInstance(r, bytes)
830        self.assertEqual(r, b'plain')
831        r = f(u'plain')
832        self.assertIsInstance(r, bytes)
833        self.assertEqual(r, b'plain')
834        r = f(b"das is' k\\303\\244se")
835        self.assertIsInstance(r, bytes)
836        self.assertEqual(r, u"das is' kÀse".encode('utf-8'))
837        r = f(u"das is' k\\303\\244se")
838        self.assertIsInstance(r, bytes)
839        self.assertEqual(r, u"das is' kÀse".encode('utf-8'))
840        r = f(b'O\\000ps\\377!')
841        self.assertEqual(r, b'O\x00ps\xff!')
842        r = f(u'O\\000ps\\377!')
843        self.assertEqual(r, b'O\x00ps\xff!')
844
845
846class TestConfigFunctions(unittest.TestCase):
847    """Test the functions for changing default settings.
848
849    The effect of most of these cannot be tested here, because that
850    needs a database connection.  So we merely test their existence here.
851
852    """
853
854    def testGetDatestyle(self):
855        self.assertIsNone(pg.get_datestyle())
856
857    def testGetDatestyle(self):
858        datestyle = pg.get_datestyle()
859        try:
860            pg.set_datestyle('ISO, YMD')
861            self.assertEqual(pg.get_datestyle(), 'ISO, YMD')
862            pg.set_datestyle('Postgres, MDY')
863            self.assertEqual(pg.get_datestyle(), 'Postgres, MDY')
864            pg.set_datestyle('Postgres, DMY')
865            self.assertEqual(pg.get_datestyle(), 'Postgres, DMY')
866            pg.set_datestyle('SQL, MDY')
867            self.assertEqual(pg.get_datestyle(), 'SQL, MDY')
868            pg.set_datestyle('SQL, DMY')
869            self.assertEqual(pg.get_datestyle(), 'SQL, DMY')
870            pg.set_datestyle('German, DMY')
871            self.assertEqual(pg.get_datestyle(), 'German, DMY')
872            pg.set_datestyle(None)
873            self.assertIsNone(pg.get_datestyle())
874        finally:
875            pg.set_datestyle(datestyle)
876
877    def testGetDecimalPoint(self):
878        r = pg.get_decimal_point()
879        self.assertIsInstance(r, str)
880        self.assertEqual(r, '.')
881
882    def testSetDecimalPoint(self):
883        point = pg.get_decimal_point()
884        try:
885            pg.set_decimal_point('*')
886            r = pg.get_decimal_point()
887            self.assertIsInstance(r, str)
888            self.assertEqual(r, '*')
889        finally:
890            pg.set_decimal_point(point)
891        r = pg.get_decimal_point()
892        self.assertIsInstance(r, str)
893        self.assertEqual(r, point)
894
895    def testGetDecimal(self):
896        r = pg.get_decimal()
897        self.assertIs(r, pg.Decimal)
898
899    def testSetDecimal(self):
900        decimal_class = pg.Decimal
901        try:
902            pg.set_decimal(int)
903            r = pg.get_decimal()
904            self.assertIs(r, int)
905        finally:
906            pg.set_decimal(decimal_class)
907        r = pg.get_decimal()
908        self.assertIs(r, decimal_class)
909
910    def testGetBool(self):
911        r = pg.get_bool()
912        self.assertIsInstance(r, bool)
913        self.assertIs(r, True)
914
915    def testSetBool(self):
916        use_bool = pg.get_bool()
917        try:
918            pg.set_bool(False)
919            r = pg.get_bool()
920            pg.set_bool(use_bool)
921            self.assertIsInstance(r, bool)
922            self.assertIs(r, False)
923            pg.set_bool(True)
924            r = pg.get_bool()
925            self.assertIsInstance(r, bool)
926            self.assertIs(r, True)
927        finally:
928            pg.set_bool(use_bool)
929        r = pg.get_bool()
930        self.assertIsInstance(r, bool)
931        self.assertIs(r, use_bool)
932
933    def testGetByteaEscaped(self):
934        r = pg.get_bytea_escaped()
935        self.assertIsInstance(r, bool)
936        self.assertIs(r, False)
937
938    def testSetByteaEscaped(self):
939        bytea_escaped = pg.get_bytea_escaped()
940        try:
941            pg.set_bytea_escaped(True)
942            r = pg.get_bytea_escaped()
943            pg.set_bytea_escaped(bytea_escaped)
944            self.assertIsInstance(r, bool)
945            self.assertIs(r, True)
946            pg.set_bytea_escaped(False)
947            r = pg.get_bytea_escaped()
948            self.assertIsInstance(r, bool)
949            self.assertIs(r, False)
950        finally:
951            pg.set_bytea_escaped(bytea_escaped)
952        r = pg.get_bytea_escaped()
953        self.assertIsInstance(r, bool)
954        self.assertIs(r, bytea_escaped)
955
956    def testGetNamedresult(self):
957        r = pg.get_namedresult()
958        self.assertTrue(callable(r))
959        self.assertIs(r, pg._namedresult)
960
961    def testSetNamedresult(self):
962        namedresult = pg.get_namedresult()
963        try:
964            pg.set_namedresult(None)
965            r = pg.get_namedresult()
966            self.assertIsNone(r)
967            f = lambda q: q.getresult()
968            pg.set_namedresult(f)
969            r = pg.get_namedresult()
970            self.assertIs(r, f)
971            self.assertRaises(TypeError, pg.set_namedresult, 'invalid')
972        finally:
973            pg.set_namedresult(namedresult)
974        r = pg.get_namedresult()
975        self.assertIs(r, namedresult)
976
977    def testGetJsondecode(self):
978        r = pg.get_jsondecode()
979        self.assertTrue(callable(r))
980        self.assertIs(r, json.loads)
981
982    def testSetJsondecode(self):
983        jsondecode = pg.get_jsondecode()
984        try:
985            pg.set_jsondecode(None)
986            r = pg.get_jsondecode()
987            self.assertIsNone(r)
988            pg.set_jsondecode(str)
989            r = pg.get_jsondecode()
990            self.assertIs(r, str)
991            self.assertRaises(TypeError, pg.set_jsondecode, 'invalid')
992        finally:
993            pg.set_jsondecode(jsondecode)
994        r = pg.get_jsondecode()
995        self.assertIs(r, jsondecode)
996
997
998class TestModuleConstants(unittest.TestCase):
999    """Test the existence of the documented module constants."""
1000
1001    def testVersion(self):
1002        v = pg.version
1003        self.assertIsInstance(v, str)
1004        # make sure the version conforms to PEP440
1005        re_version = r"""^
1006            (\d[\.\d]*(?<= \d))
1007            ((?:[abc]|rc)\d+)?
1008            (?:(\.post\d+))?
1009            (?:(\.dev\d+))?
1010            (?:(\+(?![.])[a-zA-Z0-9\.]*[a-zA-Z0-9]))?
1011            $"""
1012        match = re.match(re_version, v, re.X)
1013        self.assertIsNotNone(match)
1014
1015
1016if __name__ == '__main__':
1017    unittest.main()
Note: See TracBrowser for help on using the repository browser.