comparison env/lib/python3.7/site-packages/pyaml/tests/dump.py @ 0:26e78fe6e8c4 draft

"planemo upload commit c699937486c35866861690329de38ec1a5d9f783"
author shellac
date Sat, 02 May 2020 07:14:21 -0400
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:26e78fe6e8c4
1 # -*- coding: utf-8 -*-
2 from __future__ import unicode_literals, print_function
3
4 import itertools as it, operator as op, functools as ft
5 from collections import Mapping, OrderedDict, namedtuple
6 import os, sys, io, yaml, unittest
7
8 if sys.version_info.major > 2: unicode = str
9
10 try: import pyaml
11 except ImportError:
12 sys.path.insert(1, os.path.join(__file__, *['..']*3))
13 import pyaml
14
15
16 large_yaml = b'''
17 ### Default (baseline) configuration parameters.
18 ### DO NOT ever change this config, use -c commandline option instead!
19
20 # Note that this file is YAML, so YAML types can be used here, see http://yaml.org/type/
21 # For instance, large number can be specified as "10_000_000" or "!!float 10e6".
22
23 source:
24 # Path or glob pattern (to match path) to backup, required
25 path: # example: /srv/backups/weekly.*
26
27 queue:
28 # Path to intermediate backup queue-file (list of paths to upload), required
29 path: # example: /srv/backups/queue.txt
30 # Don't rebuild queue-file if it's newer than source.path
31 check_mtime: true
32
33 entry_cache:
34 # Path to persistent db (sqlite) of remote directory nodes, required
35 path: # example: /srv/backups/dentries.sqlite
36
37 # How to pick a path among those matched by "path" glob
38 pick_policy: alphasort_last # only one supported
39
40
41 destination:
42 # URL of Tahoe-LAFS node webapi
43 url: http://localhost:3456/uri
44
45 result: # what to do with a cap (URI) of a resulting tree (with full backup)
46 print_to_stdout: true
47 # Append the entry to the specified file (creating it, if doesn't exists)
48 # Example entry: "2012-10-10T23:12:43.904543 /srv/backups/weekly.2012-10-10 URI:DIR2-CHK:..."
49 append_to_file: # example: /srv/backups/lafs_caps
50 # Append the entry to specified tahoe-lafs directory (i.e. put it into that dir)
51 append_to_lafs_dir: # example: URI:DIR2:...
52
53 encoding:
54 xz:
55 enabled: true
56 options: # see lzma.LZMAOptions, empty = module defaults
57 min_size: 5120 # don't compress files smaller than 5 KiB (unless overidden in "path_filter")
58 path_filter:
59 # List of include/exclude regexp path-rules, similar to "filter" section below.
60 # Same as with "filter", rules can be tuples with '+' or '-' (implied for strings) as first element.
61 # '+' will indicate that file is compressible, if it's size >= "min_size" option.
62 # Unlike "filter", first element of rule-tuple can also be a number,
63 # overriding "min_size" parameter for matched (by that rule) paths.
64 # If none of the patterns match path, file is handled as if it was matched by '+' rule.
65
66 - '\.(gz|bz2|t[gb]z2?|xz|lzma|7z|zip|rar)$'
67 - '\.(rpm|deb|iso)$'
68 - '\.(jpe?g|gif|png|mov|avi|ogg|mkv|webm|mp[34g]|flv|flac|ape|pdf|djvu)$'
69 - '\.(sqlite3?|fossil|fsl)$'
70 - '\.git/objects/[0-9a-f]+/[0-9a-f]+$'
71 # - [500, '\.(txt|csv|log|md|rst|cat|(ba|z|k|c|fi)?sh|env)$']
72 # - [500, '\.(cgi|py|p[lm]|php|c|h|[ce]l|lisp|hs|patch|diff|xml|xsl|css|x?html[45]?|js)$']
73 # - [500, '\.(co?nf|cfg?|li?st|ini|ya?ml|jso?n|vg|tab)(\.(sample|default|\w+-new))?$']
74 # - [500, '\.(unit|service|taget|mount|desktop|rules|rc|menu)$']
75 # - [2000, '^/etc/']
76
77
78 http:
79 request_pool_options:
80 maxPersistentPerHost: 10
81 cachedConnectionTimeout: 600
82 retryAutomatically: true
83 ca_certs_files: /etc/ssl/certs/ca-certificates.crt # can be a list
84 debug_requests: false # insecure! logs will contain tahoe caps
85
86
87 filter:
88 # Either tuples like "[action ('+' or '-'), regexp]" or just exclude-patterns (python
89 # regexps) to match relative (to source.path, starting with "/") paths to backup.
90 # Patterns are matched against each path in order they're listed here.
91 # Leaf directories are matched with the trailing slash
92 # (as with rsync) to be distinguishable from files with the same name.
93 # If path doesn't match any regexp on the list, it will be included.
94 #
95 # Examples:
96 # - ['+', '/\.git/config$'] # backup git repository config files
97 # - '/\.git/' # *don't* backup any repository objects
98 # - ['-', '/\.git/'] # exactly same thing as above (redundant)
99 # - '/(?i)\.?svn(/.*|ignore)$' # exclude (case-insensitive) svn (or .svn) paths and ignore-lists
100
101 - '/(CVS|RCS|SCCS|_darcs|\{arch\})/$'
102 - '/\.(git|hg|bzr|svn|cvs)(/|ignore|attributes|tags)?$'
103 - '/=(RELEASE-ID|meta-update|update)$'
104
105
106 operation:
107 queue_only: false # only generate upload queue file, don't upload anything
108 reuse_queue: false # don't generate upload queue file, use existing one as-is
109 disable_deduplication: false # make no effort to de-duplicate data (should still work on tahoe-level for files)
110
111 # Rate limiting might be useful to avoid excessive cpu/net usage on nodes,
112 # and especially when uploading to rate-limited api's (like free cloud storages).
113 # Only used when uploading objects to the grid, not when building queue file.
114 # Format of each value is "interval[:burst]", where "interval" can be specified as rate (e.g. "1/3e5").
115 # Simple token bucket algorithm is used. Empty values mean "no limit".
116 # Examples:
117 # "objects: 1/10:50" - 10 objects per second, up to 50 at once (if rate was lower before).
118 # "objects: 0.1:50" - same as above.
119 # "objects: 10:20" - 1 object in 10 seconds, up to 20 at once.
120 # "objects: 5" - make interval between object uploads equal 5 seconds.
121 # "bytes: 1/3e6:50e6" - 3 MB/s max, up to 50 MB/s if connection was underutilized before.
122 rate_limit:
123 bytes: # limit on rate of *file* bytes upload, example: 1/3e5:20e6
124 objects: # limit on rate of uploaded objects, example: 10:50
125
126
127 logging: # see http://docs.python.org/library/logging.config.html
128 # "custom" level means WARNING/DEBUG/NOISE, depending on CLI options
129 warnings: true # capture python warnings
130 sql_queries: false # log executed sqlite queries (very noisy, caps will be there)
131
132 version: 1
133 formatters:
134 basic:
135 format: '%(asctime)s :: %(name)s :: %(levelname)s: %(message)s'
136 datefmt: '%Y-%m-%d %H:%M:%S'
137 handlers:
138 console:
139 class: logging.StreamHandler
140 stream: ext://sys.stderr
141 formatter: basic
142 level: custom
143 debug_logfile:
144 class: logging.handlers.RotatingFileHandler
145 filename: /srv/backups/debug.log
146 formatter: basic
147 encoding: utf-8
148 maxBytes: 5242880 # 5 MiB
149 backupCount: 2
150 level: NOISE
151 loggers:
152 twisted:
153 handlers: [console]
154 level: 0
155 root:
156 level: custom
157 handlers: [console]
158 '''
159
160 data = dict(
161 path='/some/path',
162 query_dump=OrderedDict([
163 ('key1', 'тест1'),
164 ('key2', 'тест2'),
165 ('key3', 'тест3'),
166 ('последний', None) ]),
167 ids=OrderedDict(),
168 a=[1,None,'asd', 'не-ascii'], b=3.5, c=None,
169 asd=OrderedDict([('b', 1), ('a', 2)]) )
170 data['query_dump_clone'] = data['query_dump']
171 data['ids']['id в уникоде'] = [4, 5, 6]
172 data['ids']['id2 в уникоде'] = data['ids']['id в уникоде']
173 # data["'asd'\n!\0\1"] =OrderedDict([('b', 1), ('a', 2)]) <-- fails in many ways
174
175 data_str_multiline = dict(cert=(
176 '-----BEGIN CERTIFICATE-----\n'
177 'MIIDUjCCAjoCCQD0/aLLkLY/QDANBgkqhkiG9w0BAQUFADBqMRAwDgYDVQQKFAdm\n'
178 'Z19jb3JlMRYwFAYDVQQHEw1ZZWthdGVyaW5idXJnMR0wGwYDVQQIExRTdmVyZGxv\n'
179 'dnNrYXlhIG9ibGFzdDELMAkGA1UEBhMCUlUxEjAQBgNVBAMTCWxvY2FsaG9zdDAg\n'
180 'Fw0xMzA0MjQwODUxMTRaGA8yMDUzMDQxNDA4NTExNFowajEQMA4GA1UEChQHZmdf\n'
181 'Y29yZTEWMBQGA1UEBxMNWWVrYXRlcmluYnVyZzEdMBsGA1UECBMUU3ZlcmRsb3Zz\n'
182 'a2F5YSBvYmxhc3QxCzAJBgNVBAYTAlJVMRIwEAYDVQQDEwlsb2NhbGhvc3QwggEi\n'
183 'MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCnZr3jbhfb5bUhORhmXOXOml8N\n'
184 'fAli/ak6Yv+LRBtmOjke2gFybPZFuXYr0lYGQ4KgarN904vEg7WUbSlwwJuszJxQ\n'
185 'Lz3xSDqQDqF74m1XeBYywZQIywKIbA/rfop3qiMeDWo3WavYp2kaxW28Xd/ZcsTd\n'
186 'bN/eRo+Ft1bor1VPiQbkQKaOOi6K8M9a/2TK1ei2MceNbw6YrlCZe09l61RajCiz\n'
187 'y5eZc96/1j436wynmqJn46hzc1gC3APjrkuYrvUNKORp8y//ye+6TX1mVbYW+M5n\n'
188 'CZsIjjm9URUXf4wsacNlCHln1nwBxUe6D4e2Hxh2Oc0cocrAipxuNAa8Afn5AgMB\n'
189 'AAEwDQYJKoZIhvcNAQEFBQADggEBADUHf1UXsiKCOYam9u3c0GRjg4V0TKkIeZWc\n'
190 'uN59JWnpa/6RBJbykiZh8AMwdTonu02g95+13g44kjlUnK3WG5vGeUTrGv+6cnAf\n'
191 '4B4XwnWTHADQxbdRLja/YXqTkZrXkd7W3Ipxdi0bDCOSi/BXSmiblyWdbNU4cHF/\n'
192 'Ex4dTWeGFiTWY2upX8sa+1PuZjk/Ry+RPMLzuamvzP20mVXmKtEIfQTzz4b8+Pom\n'
193 'T1gqPkNEbe2j1DciRNUOH1iuY+cL/b7JqZvvdQK34w3t9Cz7GtMWKo+g+ZRdh3+q\n'
194 '2sn5m3EkrUb1hSKQbMWTbnaG4C/F3i4KVkH+8AZmR9OvOmZ+7Lo=\n'
195 '-----END CERTIFICATE-----' ))
196
197 data_str_long = dict(cert=(
198 'MIIDUjCCAjoCCQD0/aLLkLY/QDANBgkqhkiG9w0BAQUFADBqMRAwDgYDVQQKFAdm'
199 'Z19jb3JlMRYwFAYDVQQHEw1ZZWthdGVyaW5idXJnMR0wGwYDVQQIExRTdmVyZGxv'
200 'dnNrYXlhIG9ibGFzdDELMAkGA1UEBhMCUlUxEjAQBgNVBAMTCWxvY2FsaG9zdDAg'
201 'Fw0xMzA0MjQwODUxMTRaGA8yMDUzMDQxNDA4NTExNFowajEQMA4GA1UEChQHZmdf'
202 'Y29yZTEWMBQGA1UEBxMNWWVrYXRlcmluYnVyZzEdMBsGA1UECBMUU3ZlcmRsb3Zz'
203 'a2F5YSBvYmxhc3QxCzAJBgNVBAYTAlJVMRIwEAYDVQQDEwlsb2NhbGhvc3QwggEi'
204 'MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCnZr3jbhfb5bUhORhmXOXOml8N'
205 'fAli/ak6Yv+LRBtmOjke2gFybPZFuXYr0lYGQ4KgarN904vEg7WUbSlwwJuszJxQ'
206 'Lz3xSDqQDqF74m1XeBYywZQIywKIbA/rfop3qiMeDWo3WavYp2kaxW28Xd/ZcsTd'
207 'bN/eRo+Ft1bor1VPiQbkQKaOOi6K8M9a/2TK1ei2MceNbw6YrlCZe09l61RajCiz'
208 'y5eZc96/1j436wynmqJn46hzc1gC3APjrkuYrvUNKORp8y//ye+6TX1mVbYW+M5n'
209 'CZsIjjm9URUXf4wsacNlCHln1nwBxUe6D4e2Hxh2Oc0cocrAipxuNAa8Afn5AgMB'
210 'AAEwDQYJKoZIhvcNAQEFBQADggEBADUHf1UXsiKCOYam9u3c0GRjg4V0TKkIeZWc'
211 'uN59JWnpa/6RBJbykiZh8AMwdTonu02g95+13g44kjlUnK3WG5vGeUTrGv+6cnAf'
212 '4B4XwnWTHADQxbdRLja/YXqTkZrXkd7W3Ipxdi0bDCOSi/BXSmiblyWdbNU4cHF/'
213 'Ex4dTWeGFiTWY2upX8sa+1PuZjk/Ry+RPMLzuamvzP20mVXmKtEIfQTzz4b8+Pom'
214 'T1gqPkNEbe2j1DciRNUOH1iuY+cL/b7JqZvvdQK34w3t9Cz7GtMWKo+g+ZRdh3+q'
215 '2sn5m3EkrUb1hSKQbMWTbnaG4C/F3i4KVkH+8AZmR9OvOmZ+7Lo=' ))
216
217
218 # Restore Python2-like heterogeneous list sorting functionality in Python3
219 # Based on https://gist.github.com/pR0Ps/1e1a1e892aad5b691448
220 def compare(x, y):
221 if x == y: return 0
222 try:
223 if x < y: return -1
224 else: return 1
225
226 except TypeError as e:
227 # The case where both are None is taken care of by the equality test
228 if x is None: return -1
229 elif y is None: return 1
230
231 if type(x) != type(y):
232 return compare(*map(lambda t: type(t).__name__, [x, y]))
233
234 # Types are the same but a native compare didn't work.
235 # x and y might be indexable, recursively compare elements
236 for a, b in zip(x, y):
237 c = compare(a, b)
238 if c != 0: return c
239
240 return compare(len(x), len(y))
241
242
243 class DumpTests(unittest.TestCase):
244
245 def flatten(self, data, path=tuple()):
246 dst = list()
247 if isinstance(data, (tuple, list)):
248 for v in data:
249 dst.extend(self.flatten(v, path + (list,)))
250 elif isinstance(data, Mapping):
251 for k,v in data.items():
252 dst.extend(self.flatten(v, path + (k,)))
253 else: dst.append((path, data))
254 return tuple(sorted(dst, key=ft.cmp_to_key(compare)))
255
256 def test_dst(self):
257 buff = io.BytesIO()
258 self.assertIs(pyaml.dump(data, buff), None)
259 self.assertIsInstance(pyaml.dump(data, str), str)
260 self.assertIsInstance(pyaml.dump(data, unicode), unicode)
261
262 def test_simple(self):
263 a = self.flatten(data)
264 b = pyaml.dump(data, unicode)
265 self.assertEqual(a, self.flatten(yaml.safe_load(b)))
266
267 def test_vspacing(self):
268 data = yaml.safe_load(large_yaml)
269 a = self.flatten(data)
270 b = pyaml.dump(data, unicode, vspacing=[2, 1])
271 self.assertEqual(a, self.flatten(yaml.safe_load(b)))
272 pos, pos_list = 0, list()
273 while True:
274 pos = b.find(u'\n', pos+1)
275 if pos < 0: break
276 pos_list.append(pos)
277 self.assertEqual( pos_list,
278 [ 12, 13, 25, 33, 53, 74, 89, 108, 158, 185, 265, 300, 345, 346, 356, 376, 400, 426, 427,
279 460, 461, 462, 470, 508, 564, 603, 604, 605, 611, 612, 665, 666, 690, 691, 715, 748,
280 777, 806, 807, 808, 817, 818, 832, 843, 878, 948, 949, 961, 974, 1009, 1032, 1052,
281 1083, 1102, 1123, 1173, 1195, 1234, 1257, 1276, 1300, 1301, 1312, 1325, 1341, 1359,
282 1374, 1375, 1383, 1397, 1413, 1431, 1432, 1453, 1454, 1467, 1468, 1485, 1486, 1487,
283 1498, 1499, 1530, 1531, 1551, 1552, 1566, 1577, 1590, 1591, 1612, 1613, 1614, 1622,
284 1623, 1638, 1648, 1649, 1657, 1658, 1688, 1689, 1698, 1720, 1730 ] )
285 b = pyaml.dump(data, unicode)
286 self.assertNotIn('\n\n', b)
287
288 def test_ids(self):
289 b = pyaml.dump(data, unicode)
290 self.assertNotIn('&id00', b)
291 self.assertIn('query_dump_clone: *query_dump_clone', b)
292 self.assertIn("'id в уникоде': &ids_-_id2_v_unikode", b) # kinda bug - should be just "id"
293
294 def test_force_embed(self):
295 b = pyaml.dump(data, unicode, force_embed=True)
296 c = pyaml.dump(data, unicode, safe=True, force_embed=True)
297 for char, dump in it.product('*&', [b, c]):
298 self.assertNotIn(char, dump)
299
300 def test_encoding(self):
301 b = pyaml.dump(data, unicode, force_embed=True)
302 b_lines = list(map(unicode.strip, b.splitlines()))
303 chk = ['query_dump:', 'key1: тест1', 'key2: тест2', 'key3: тест3', 'последний:']
304 pos = b_lines.index('query_dump:')
305 self.assertEqual(b_lines[pos:pos + len(chk)], chk)
306
307 def test_str_long(self):
308 b = pyaml.dump(data_str_long, unicode)
309 self.assertNotIn('"', b)
310 self.assertNotIn("'", b)
311 self.assertEqual(len(b.splitlines()), 1)
312
313 def test_str_multiline(self):
314 b = pyaml.dump(data_str_multiline, unicode)
315 b_lines = b.splitlines()
316 self.assertGreater(len(b_lines), len(data_str_multiline['cert'].splitlines()))
317 for line in b_lines: self.assertLess(len(line), 100)
318
319 def test_dumps(self):
320 b = pyaml.dumps(data_str_multiline)
321 self.assertIsInstance(b, bytes)
322
323 def test_print(self):
324 self.assertIs(pyaml.print, pyaml.pprint)
325 self.assertIs(pyaml.print, pyaml.p)
326 buff = io.BytesIO()
327 b = pyaml.dump(data_str_multiline, dst=bytes)
328 pyaml.print(data_str_multiline, file=buff)
329 self.assertEqual(b, buff.getvalue())
330
331 def test_print_args(self):
332 buff = io.BytesIO()
333 args = 1, 2, 3
334 b = pyaml.dump(args, dst=bytes)
335 pyaml.print(*args, file=buff)
336 self.assertEqual(b, buff.getvalue())
337
338 def test_str_style_pick(self):
339 a = pyaml.dump(data_str_multiline)
340 b = pyaml.dump(data_str_multiline, string_val_style='|')
341 self.assertEqual(a, b)
342 b = pyaml.dump(data_str_multiline, string_val_style='plain')
343 self.assertNotEqual(a, b)
344 self.assertTrue(pyaml.dump('waka waka', string_val_style='|').startswith('|-\n'))
345 self.assertEqual(pyaml.dump(dict(a=1), string_val_style='|'), 'a: 1\n')
346
347 def test_colons_in_strings(self):
348 val1 = {'foo': ['bar:', 'baz', 'bar:bazzo', 'a: b'], 'foo:': 'yak:'}
349 val1_str = pyaml.dump(val1)
350 val2 = yaml.safe_load(val1_str)
351 val2_str = pyaml.dump(val2)
352 val3 = yaml.safe_load(val2_str)
353 self.assertEqual(val1, val2)
354 self.assertEqual(val1_str, val2_str)
355 self.assertEqual(val2, val3)
356
357 def test_empty_strings(self):
358 val1 = {'key': ['', 'stuff', '', 'more'], '': 'value', 'k3': ''}
359 val1_str = pyaml.dump(val1)
360 val2 = yaml.safe_load(val1_str)
361 val2_str = pyaml.dump(val2)
362 val3 = yaml.safe_load(val2_str)
363 self.assertEqual(val1, val2)
364 self.assertEqual(val1_str, val2_str)
365 self.assertEqual(val2, val3)
366
367 def test_single_dash_strings(self):
368 strip_seq_dash = lambda line: line.lstrip().lstrip('-').lstrip()
369 val1 = {'key': ['-', '-stuff', '- -', '- more-', 'more-', '--']}
370 val1_str = pyaml.dump(val1)
371 val2 = yaml.safe_load(val1_str)
372 val2_str = pyaml.dump(val2)
373 val3 = yaml.safe_load(val2_str)
374 self.assertEqual(val1, val2)
375 self.assertEqual(val1_str, val2_str)
376 self.assertEqual(val2, val3)
377 val1_str_lines = val1_str.splitlines()
378 self.assertEqual(strip_seq_dash(val1_str_lines[2]), '-stuff')
379 self.assertEqual(strip_seq_dash(val1_str_lines[5]), 'more-')
380 self.assertEqual(strip_seq_dash(val1_str_lines[6]), '--')
381 val1 = {'key': '-'}
382 val1_str = pyaml.dump(val1)
383 val2 = yaml.safe_load(val1_str)
384 val2_str = pyaml.dump(val2)
385 val3 = yaml.safe_load(val2_str)
386
387 def test_namedtuple(self):
388 TestTuple = namedtuple('TestTuple', 'y x z')
389 val = TestTuple(1, 2, 3)
390 val_str = pyaml.dump(val)
391 self.assertEqual(val_str, u'y: 1\nx: 2\nz: 3\n') # namedtuple order was preserved
392
393 def test_ordereddict(self):
394 d = OrderedDict((i, '') for i in reversed(range(10)))
395 lines = pyaml.dump(d).splitlines()
396 self.assertEqual(lines, list(reversed(sorted(lines))))
397
398 def test_pyyaml_params(self):
399 d = {'foo': 'lorem ipsum ' * 30} # 300+ chars
400 for w in 40, 80, 200:
401 lines = pyaml.dump(d, width=w, indent=10).splitlines()
402 for n, line in enumerate(lines, 1):
403 self.assertLess(len(line), w*1.2)
404 if n != len(lines): self.assertGreater(len(line), w*0.8)
405
406 def test_multiple_docs(self):
407 docs = [yaml.safe_load(large_yaml), dict(a=1, b=2, c=3)]
408 docs_str = pyaml.dump_all(docs, vspacing=[3, 2])
409 self.assertTrue(docs_str.startswith('---'))
410 self.assertIn('---\n\n\n\na: 1\n\n\n\nb: 2\n\n\n\nc: 3\n', docs_str)
411 docs_str2 = pyaml.dump(docs, vspacing=[3, 2], multiple_docs=True)
412 self.assertEqual(docs_str, docs_str2)
413 docs_str2 = pyaml.dump(docs, vspacing=[3, 2])
414 self.assertNotEqual(docs_str, docs_str2)
415 docs_str2 = pyaml.dump_all(docs, explicit_start=False)
416 self.assertFalse(docs_str2.startswith('---'))
417 self.assertNotEqual(docs_str, docs_str2)
418 docs_str = pyaml.dump(docs, multiple_docs=True, explicit_start=False)
419 self.assertEqual(docs_str, docs_str2)
420
421 def test_ruamel_yaml(self):
422 try: from ruamel.yaml import YAML
423 except ImportError: return unittest.skip('No ruamel.yaml module to test it')
424 data = YAML(typ='safe').load(large_yaml)
425 yaml_str = pyaml.dump(data)
426
427
428 if __name__ == '__main__':
429 unittest.main()
430 # print('-'*80)
431 # pyaml.dump(yaml.safe_load(large_yaml), sys.stdout)
432 # print('-'*80)
433 # pyaml.dump(data, sys.stdout)