1 '''
2 Introduction
3 ============
4
5 Based on the standard Python ``ConfigParser`` module, this module provides an
6 enhanced configuration parser capabilities. ``Configuration`` is a drop-in
7 replacement for ``ConfigParser``.
8
9 A configuration file is broken into sections, and each section is
10 introduced by a section name in brackets. For example::
11
12 [main]
13 installationDirectory=/usr/local/foo
14 programDirectory: /usr/local/foo/programs
15
16 [search]
17 searchCommand: find /usr/local/foo -type f -name "*.class"
18
19 [display]
20 searchFailedMessage=Search failed, sorry.
21
22
23 Section Name Syntax
24 ===================
25
26 A section name can consist of alphabetics, numerics, underscores and
27 periods. There can be any amount of whitespace before and after the
28 brackets in a section name; the whitespace is ignored.
29
30 Variable Syntax
31 ===============
32
33 Each section contains zero or more variable settings.
34
35 - Similar to a Java ``Properties`` file, the variables are specified as
36 name/value pairs, separated by an equal sign ("=") or a colon (":").
37 - Variable names are case-sensitive and may contain alphabetics, numerics,
38 underscores and periods (".").
39 - Variable values may contain anything at all. Leading whitespace in the
40 value is skipped. The way to include leading whitespace in a value is
41 escape the whitespace characters with backslashes.
42
43 Variable Substitution
44 =====================
45
46 A variable value can interpolate the values of other variables, using a
47 variable substitution syntax. The general form of a variable reference is
48 ``${section_name:var_name}``.
49
50 - *section_name* is the name of the section containing the variable to
51 substitute; if omitted, it defaults to the current section.
52 - *var_name* is the name of the variable to substitute.
53
54 Default values
55 --------------
56
57 You can also specify a default value for a variable, using this syntax::
58
59 ${foo?default}
60 ${section:foo?default}
61
62 That is, the sequence ``?default`` after a variable name specifies the
63 default value if the variable has no value. (Normally, if a variable has
64 no value, it is replaced with an empty string.) Defaults can be useful,
65 for instance, to allow overrides from the environment. The following example
66 defines a log file directory that defaults to "/tmp", unless environment
67 variable LOGDIR is set to a non-empty value::
68
69 logDirectory: ${env:LOGDIR?/var/log}
70
71 Special section names
72 ---------------------
73
74 The section names "env", and "program" are reserved for special
75 pseudosections.
76
77 The ``env`` pseudosection
78 ~~~~~~~~~~~~~~~~~~~~~~~~~
79
80 The "env" pseudosection is used to interpolate values from the environment. On
81 UNIX systems, for instance, ``${env:HOME}`` substitutes home directory of the
82 current user. On some versions of Windows, ``${env:USERNAME}`` will substitute
83 the name of the user.
84
85 Note: On UNIX systems, environment variable names are typically
86 case-sensitive; for instance, ``${env:USER}`` and ``${env:user}`` refer to
87 different environment variables. On Windows systems, environment variable
88 names are typically case-insensitive; ``${env:USERNAME}`` and
89 ``${env:username}`` are equivalent.
90
91 The ``program`` pseudosection
92 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
93
94 The "program" pseudosection is a placeholder for various special variables
95 provided by the Configuration class. Those variables are:
96
97 ``cwd``
98 The current working directory. Thus, ``${program:cwd}`` will substitute
99 the working directory, using the appropriate system-specific
100 file separator (e.g., "/" on Unix, "\\" on Windows).
101
102 ``name``
103 The calling program name. Equivalent to the Python expression
104 ``os.path.basename(sys.argv[0])``
105
106 ``now``
107 The current time, formatted using the ``time.strftime()`` format
108 ``"%Y-%m-%d %H:%M:%S"`` (e.g., "2008-03-03 16:15:27")
109
110 Includes
111 --------
112
113 A special include directive permits inline inclusion of another
114 configuration file. The include directive takes two forms::
115
116 %include "path"
117 %include "URL"
118
119 For example::
120
121 %include "/home/bmc/mytools/common.cfg"
122 %include "http://configs.example.com/mytools/common.cfg"
123
124 The included file may contain any content that is valid for this parser. It
125 may contain just variable definitions (i.e., the contents of a section,
126 without the section header), or it may contain a complete configuration
127 file, with individual sections.
128
129 Note: Attempting to include a file from itself, either directly or
130 indirectly, will cause the parser to throw an exception.
131
132 Replacing ``ConfigParser``
133 ==========================
134
135 You can use this class anywhere you would use the standard Python
136 ``ConfigParser`` class. Thus, to change a piece of code to use enhanced
137 configuration, you might change this:
138
139 .. python::
140
141 import ConfigParser
142
143 config = ConfigParser.SafeConfigParser()
144 config.read(configPath)
145
146 to this:
147
148 .. python::
149
150 from grizzled.config import Configuration
151
152 config = Configuration()
153 config.read(configPath)
154
155
156 Sometimes, however, you have to use an API that expects a path to a
157 configuration file that can *only* be parsed with the (unenhanced)
158 ``ConfigParser`` class. In that case, you simply use the ``preprocess()``
159 method:
160
161 .. python::
162
163 import logging
164 from grizzled import config
165
166 logging.config.fileConfig(config.preprocess(pathToConfig))
167
168 That will preprocess the enhanced configuration file, producing a file
169 that is suitable for parsing by the standard Python ``config`` module.
170 '''
171
172 from __future__ import absolute_import
173
174 __docformat__ = "restructuredtext en"
175
176
177
178
179
180 import ConfigParser
181 import logging
182 import string
183 import os
184 import time
185 import sys
186 import re
187
188 from grizzled.exception import ExceptionWithMessage
189 from grizzled.collections import OrderedDict
190
191
192
193
194
195 __all__ = ['Configuration', 'preprocess',
196 'NoOptionError', 'NoSectionError', 'NoVariableError']
197
198
199
200
201
202 log = logging.getLogger('grizzled.config')
203 NoOptionError = ConfigParser.NoOptionError
204 NoSectionError = ConfigParser.NoSectionError
205
206
207
208
209
210
211
212 SECTION_OPTION_DELIM = r':'
213
214
215 SECTION_NAME_PATTERN = r'([_.a-zA-Z][_.a-zA-Z0-9]+)'
216
217
218 VARIABLE_NAME_PATTERN = r'([_a-zA-Z][_a-zA-Z0-9]+)(\?[^}]+)?'
219
220
221
222
223
224
225
226
227
228
229 VARIABLE_REF_PATTERN = SECTION_NAME_PATTERN + SECTION_OPTION_DELIM +\
230 VARIABLE_NAME_PATTERN +\
231 r'|' +\
232 VARIABLE_NAME_PATTERN
233
234
235 SIMPLE_VARIABLE_REF_PATTERN = r'\$\{' + VARIABLE_NAME_PATTERN + '\}'
236
237
238 ENV_SECTION = 'env'
239 PROGRAM_SECTION = 'program'
246 """
247 Thrown when a configuration file attempts to substitute a nonexistent
248 variable, and the ``Configuration`` object was instantiated with
249 ``strict_substitution`` set to ``True``.
250 """
251 pass
252
254 """
255 Configuration file parser. See the module documentation for details.
256 """
257
258 - def __init__(self,
259 defaults=None,
260 permit_includes=True,
261 use_ordered_sections=False,
262 strict_substitution=False):
263 """
264 Construct a new ``Configuration`` object.
265
266 :Parameters:
267 defaults : dict
268 dictionary of default values
269 permit_includes : bool
270 whether or not to permit includes
271 use_ordered_sections : bool
272 whether or not to use an ordered dictionary for the section
273 names. If ``True``, then a call to ``sections()`` will return
274 the sections in the order they were encountered in the file.
275 If ``False``, the order is based on the hash keys for the
276 sections' names.
277 strict_substitution : bool
278 If ``True``, then throw an exception if attempting to
279 substitute a non-existent variable. Otherwise, simple
280 substitute an empty value.
281 """
282 ConfigParser.SafeConfigParser.__init__(self, defaults)
283 self.__permit_includes = permit_includes
284 self.__use_ordered_sections = use_ordered_sections
285 self.__strict_substitution = strict_substitution
286
287 if use_ordered_sections:
288 self._sections = OrderedDict()
289
291 """
292 Returns the instance-wide defaults.
293
294 :rtype: dict
295 :return: the instance-wide defaults, or ``None`` if there aren't any
296 """
297 return ConfigParser.SafeConfigParser.defaults(self)
298
299 @property
301 """
302 Get the list of available sections, not including ``DEFAULT``. It's
303 not really useful to call this method before calling ``read()`` or
304 ``readfp()``.
305
306 Returns a list of sections.
307 """
308 return ConfigParser.SafeConfigParser.sections(self)
309
311 """
312 Add a section named *section* to the instance. If a section by the
313 given name already exists, ``DuplicateSectionError`` is raised.
314
315 :Parameters:
316 section : str
317 name of section to add
318
319 :raise DuplicateSectionError: section already exists
320 """
321 ConfigParser.SafeConfigParser.add_section(self, section)
322
324 """
325 Determine whether a section exists in the configuration. Ignores
326 the ``DEFAULT`` section.
327
328 :Parameters:
329 section : str
330 name of section
331
332 :rtype: bool
333 :return: ``True`` if the section exists in the configuration, ``False``
334 if not.
335 """
336 return ConfigParser.SafeConfigParser.has_section(self, section)
337
339 """
340 Get a list of options available in the specified section.
341
342 :Parameters:
343 section : str
344 name of section
345
346 :rtype: list
347 :return: list of available options. May be empty.
348
349 :raise NoSectionError: no such section
350 """
351 return ConfigParser.SafeConfigParser.options(self, section)
352
354 """
355 Determine whether a section has a specific option.
356
357 :Parameters:
358 section : str
359 name of section
360 option : str
361 name of option to check
362
363 :rtype: bool
364 :return: ``True`` if the section exists in the configuration and
365 has the specified option, ``False`` if not.
366 """
367 return ConfigParser.SafeConfigParser.has_option(self, section, option)
368
369 - def read(self, filenames):
370 """
371 Attempt to read and parse a list of filenames or URLs, returning a
372 list of filenames or URLs which were successfully parsed. If
373 *filenames* is a string or Unicode string, it is treated as a single
374 filename or URL. If a file or URL named in filenames cannot be opened,
375 that file will be ignored. This is designed so that you can specify a
376 list of potential configuration file locations (for example, the
377 current directory, the user's home directory, and some system-wide
378 directory), and all existing configuration files in the list will be
379 read. If none of the named files exist, the ``Configuration`` instance
380 will contain an empty dataset. An application which requires initial
381 values to be loaded from a file should load the required file or files
382 using ``readfp()`` before calling ``read()`` for any optional files:
383
384 .. python::
385
386 import Configuration
387 import os
388
389 config = Configuration.Configuration()
390 config.readfp(open('defaults.cfg'))
391 config.read(['site.cfg', os.path.expanduser('~/.myapp.cfg')])
392
393 :Parameters:
394 filenames : list or string
395 list of file names or URLs, or the string for a single filename
396 or URL
397
398 :rtype: list
399 :return: list of successfully parsed filenames or URLs
400 """
401 if isinstance(filenames, basestring):
402 filenames = [filenames]
403
404 newFilenames = []
405 for filename in filenames:
406 try:
407 self.__preprocess(filename, filename)
408 newFilenames += [filename]
409 except IOError:
410 log.exception('Error reading "%s"' % filename)
411
412 return newFilenames
413
414 - def readfp(self, fp, filename=None):
415 '''
416 Read and parse configuration data from a file or file-like object.
417 (Only the ``readline()`` moethod is used.)
418
419 :Parameters:
420 fp : file
421 File-like object with a ``readline()`` method
422 filename : str
423 Name associated with ``fp``, for error messages. If omitted or
424 ``None``, then ``fp.name`` is used. If ``fp`` has no ``name``
425 attribute, then ``"<???">`` is used.
426 '''
427 self.__preprocess(fp, filename)
428
429 - def get(self, section, option, optional=False):
430 """
431 Get an option from a section.
432
433 :Parameters:
434 section : str
435 name of section
436 option : str
437 name of option to check
438 optional : bool
439 ``True`` to return None if the option doesn't exist. ``False``
440 to throw an exception if the option doesn't exist.
441
442 :rtype: str
443 :return: the option value
444
445 :raise NoSectionError: no such section
446 :raise NoOptionError: no such option in the section
447 """
448 def do_get(section, option):
449 val = ConfigParser.SafeConfigParser.get(self, section, option)
450 if len(val.strip()) == 0:
451 raise ConfigParser.NoOptionError(option, section)
452 return val
453
454 if optional:
455 return self.__get_optional(do_get, section, option)
456 else:
457 return do_get(section, option)
458
459 - def getint(self, section, option, optional=False):
460 """
461 Convenience method that coerces the result of a call to
462 ``get()`` to an ``int``.
463
464 :Parameters:
465 section : str
466 name of section
467 option : str
468 name of option to check
469 optional : bool
470 ``True`` to return None if the option doesn't exist. ``False``
471 to throw an exception if the option doesn't exist.
472
473 :rtype: int
474 :return: the option value
475
476 :raise NoSectionError: no such section
477 :raise NoOptionError: no such option in the section
478 """
479 def do_get(section, option):
480 return ConfigParser.SafeConfigParser.getint(self, section, option)
481
482 if optional:
483 return self.__get_optional(do_xget, section, option)
484 else:
485 return do_get(section, option)
486
487 - def getfloat(self, section, option, optional=False):
488 """
489 Convenience method that coerces the result of a call to ``get()`` to a
490 ``float``.
491
492 :Parameters:
493 section : str
494 name of section
495 option : str
496 name of option to check
497 optional : bool
498 ``True`` to return None if the option doesn't exist. ``False``
499 to throw an exception if the option doesn't exist.
500
501 :rtype: float
502 :return: the option value
503
504 :raise NoSectionError: no such section
505 :raise NoOptionError: no such option in the section
506 """
507 def do_get(section, option):
508 return ConfigParser.SafeConfigParser.getfloat(self, section, option)
509
510 if optional:
511 return self.__get_optional(do_get, section, option)
512 else:
513 return do_get(section, option)
514
515 - def getboolean(self, section, option, optional=False):
516 '''
517 Convenience method that coerces the result of a call to ``get()`` to a
518 boolean. Accepted boolean values are "1", "yes", "true", and "on",
519 which cause this method to return True, and "0", "no", "false", and
520 "off", which cause it to return False. These string values are checked
521 in a case-insensitive manner. Any other value will cause it to raise
522 ``ValueError``.
523
524 :Parameters:a
525 section : str
526 name of section
527 option : str
528 name of option to check
529 optional : bool
530 ``True`` to return None if the option doesn't exist. ``False``
531 to throw an exception if the option doesn't exist.
532
533 :rtype: bool
534 :return: the option value (``True`` or ``False``)
535
536 :raise NoSectionError: no such section
537 :raise NoOptionError: no such option in the section
538 :raise ValueError: non-boolean value encountered
539 '''
540 def do_get(section, option):
541 return ConfigParser.SafeConfigParser.getboolean(self,
542 section,
543 option)
544
545 if optional:
546 return self.__get_optional(do_get, section, option)
547 else:
548 return do_get(section, option)
549
550 - def getlist(self, section, option, sep=None, optional=False):
551 '''
552 Convenience method that coerces the result of a call to ``get()`` to a
553 list. The value is split using the separator(s) specified by the
554 ``sep`` argument. A ``sep`` value of ``None`` uses white space. The
555 result is a list of string values.
556
557 :Parameters:
558 section : str
559 name of section
560 option : str
561 name of option to check
562 sep : str
563 list element separator to use. Defaults to white space.
564 optional : bool
565 ``True`` to return None if the option doesn't exist. ``False``
566 to throw an exception if the option doesn't exist.
567
568 :rtype: bool
569 :return: the option value (``True`` or ``False``)
570
571 :raise NoSectionError: no such section
572 :raise NoOptionError: no such option in the section
573 '''
574 def do_get(section, option):
575 value = ConfigParser.SafeConfigParser.get(self, section, option)
576 return value.split(sep)
577
578 if optional:
579 return self.__get_optional(do_get, section, option)
580 else:
581 return do_get(section, option)
582
583 - def get_one_of(self,
584 section,
585 options,
586 optional=False,
587 default=None,
588 value_type=str):
589 '''
590 Retrieve at most one of a list or set of options from a section. This
591 method is useful if there are multiple possible names for a single
592 option. For example, suppose you permit either a ``user_name`` or a
593 ``login_name`` option, but not both, in a section called
594 ``credentials``. You can use the following code to retrieve the
595 option value:
596
597 .. python::
598
599 from grizzled.config import Configuration
600
601 config = Configuration()
602 config.read('/path/to/config')
603 user = config.get_one_of('credentials', ['user_name', 'login_name'])
604
605 If both options exist, ``get_one_of()`` will a ``NoOptionError``. If
606 neither option exists, ``get_one_of()`` will throw a ``NoOptionError``
607 if ``optional`` is ``False`` and there's no default value; otherwise,
608 it will return the default value.
609
610 :Parameters:
611 section : str
612 name of section
613 options : list or set
614 list or set of allowable option names
615 optional : bool
616 ``True`` to return None if the option doesn't exist. ``False``
617 to throw an exception if the option doesn't exist.
618 default : str
619 The default value, if the option does not exist.
620 value_type : type
621 The type to which to coerce the value. The value is coerced by
622 casting.
623
624 :rtype: str
625 :return: the option value, or ``None`` if nonexistent and
626 ``optional`` is ``True``
627
628 :raise NoSectionError: no such section
629 :raise NoOptionError: none of the named options are in the section
630 '''
631 value = None
632 if value_type is bool:
633 get = self.getboolean
634 else:
635 get = self.get
636
637 for option in options:
638 value = get(section, option, optional=True)
639 if value:
640 break
641
642 if value is None:
643 value = default
644
645 if (value is None) and (not optional):
646 raise NoOptionError('Section "%s" must contain exactly one of the '
647 'following options: %s' %
648 (section, ', '.join(list(options))))
649
650 if value is not None:
651 if not (value_type in (bool, str)):
652 value = eval('%s(%s)' % (value_type.__name__, value))
653
654 return value
655
656 - def items(self, section):
657 """
658 Get all items in a section.
659
660 :Parameters:
661 section : str
662 name of section
663
664 :rtype: list
665 :return: a list of (*name*, *value*) tuples for each option in
666 in *section*
667
668 :raise NoSectionError: no such section
669 """
670 return ConfigParser.SafeConfigParser.items(self, section)
671
672 - def set(self, section, option, value):
673 """
674 If the given section exists, set the given option to the specified
675 value; otherwise raise ``NoSectionError``.
676
677 :Parameters:
678 section : str
679 name of section
680 option : str
681 name of option to check
682 value : str
683 the value to set
684
685 :raise NoSectionError: no such section
686 """
687 ConfigParser.SafeConfigParser.set(self, section, option, value)
688
689 - def write(self, fileobj):
690 """
691 Write a representation of the configuration to the specified file-like
692 object. This output can be parsed by a future ``read()`` call.
693
694 NOTE: Includes and variable references are ``not`` reconstructed.
695 That is, the configuration data is written in *expanded* form.
696
697 :Parameters:
698 fileobj : file
699 file-like object to which to write the configuration
700 """
701 ConfigParser.SafeConfigParser.write(self, fileobj)
702
704 """
705 Remove a section from the instance. If a section by the given name
706 does not exist, ``NoSectionError`` is raised.
707
708 :Parameters:
709 section : str
710 name of section to remove
711
712 :raise NoSectionError: no such section
713 """
714 ConfigParser.SafeConfigParser.remove_section(self, section)
715
727
729 try:
730 return func(section, option)
731 except ConfigParser.NoOptionError:
732 return None
733 except ConfigParser.NoSectionError:
734 return None
735
770
772 """
773 Convert all section-local variable references (i.e., those that don't
774 specify a section) to fully-qualified references. Necessary for
775 recursive references to work.
776 """
777 simpleVarRefRe = re.compile(SIMPLE_VARIABLE_REF_PATTERN)
778 for section in sourceConfig.sections():
779 for option in sourceConfig.options(section):
780 value = sourceConfig.get(section, option, raw=True)
781 oldValue = value
782 match = simpleVarRefRe.search(value)
783 while match:
784 value = value[0:match.start(1)] +\
785 section +\
786 SECTION_OPTION_DELIM +\
787 value[match.start(1):]
788 match = simpleVarRefRe.search(value)
789
790 sourceConfig.set(section, option, value)
791
793 mapping = _ConfigDict(sourceConfig, self.__strict_substitution)
794 for section in sourceConfig.sections():
795 mapping.section = section
796 self.add_section(section)
797 for option in sourceConfig.options(section):
798 value = sourceConfig.get(section, option, raw=True)
799
800
801
802 previousValue = ''
803 while value != previousValue:
804 previousValue = value
805 value = _ConfigTemplate(value).safe_substitute(mapping)
806
807 self.set(section, option, value)
808
810 """
811 Subclass of string.Template that handles our configuration variable
812 reference syntax.
813 """
814 idpattern = VARIABLE_REF_PATTERN
815
817 """
818 Dictionary that knows how to dereference variables within a parsed config.
819 Only used internally.
820 """
821 idPattern = re.compile(VARIABLE_REF_PATTERN)
822 - def __init__(self, parsedConfig, strict_substitution):
823 self.__config = parsedConfig
824 self.__strict_substitution = strict_substitution
825 self.section = None
826
828 try:
829
830
831
832 match = self.idPattern.search(key)
833 assert(match)
834
835
836
837 default = None
838 if SECTION_OPTION_DELIM in key:
839 if match.group(3):
840 default = self.__extract_default(match.group(3))
841
842 section = match.group(1)
843 option = match.group(2)
844 else:
845 section = self.section
846 default = None
847 option = match.group(3)
848 if match.group(4):
849 default = self.__extract_default(match.group(3))
850
851 result = self.__value_from_section(section, option)
852
853 except KeyError:
854 result = default
855
856 except ConfigParser.NoSectionError:
857 result = default
858
859 except ConfigParser.NoOptionError:
860 result = default
861
862 if not result:
863 if self.__strict_substitution:
864 raise NoVariableError, 'No such variable: "%s"' % key
865 else:
866 result = ''
867
868 return result
869
871 default = s
872 if default:
873 default = default[1:]
874 if len(default) == 0:
875 default = None
876
877 return default
878
880 return {
881 'cwd' : os.getcwd(),
882 'now' : time.strftime('%Y-%m-%d %H:%M:%S'),
883 'name' : os.path.basename(sys.argv[0])
884 }[option]
885
887 result = None
888 if section == 'env':
889 result = os.environ[option]
890 if len(result) == 0:
891 raise KeyError, option
892
893 elif section == 'program':
894 result = self.__value_from_program_section(option)
895
896 else:
897 result = self.__config.get(section, option)
898
899 return result
900
901
902
903
904
905 -def preprocess(file_or_url, defaults=None):
906 """
907 This function preprocesses a file or URL for a configuration file,
908 processing all includes and substituting all variables. It writes a new
909 configuration file to a temporary file (or specified output file). The
910 new configuration file can be read by a standard ``ConfigParser``
911 object. Thus, this method is useful when you have an extended
912 configuration file that must be passed to a function or object that can
913 only read a standard ``ConfigParser`` file.
914
915 For example, here's how you might use the Python ``logging`` API with an
916 extended configuration file:
917
918 .. python::
919
920 from grizzled.config import Configuration
921 import logging
922
923 logging.config.fileConfig(Configuration.preprocess('/path/to/config')
924
925 :Parameters:
926 file_or_url : str
927 file or URL to read and preprocess
928 defaults : dict
929 defaults to pass through to the config parser
930
931 :rtype: string
932 :return: Path to a temporary file containing the expanded configuration.
933 The file will be deleted when the program exits, though the caller
934 is free to delete it sooner.
935 """
936 import tempfile
937 import atexit
938
939 def unlink(path):
940 try:
941 os.unlink(path)
942 except:
943 pass
944
945 parser = Configuration(use_ordered_sections=True)
946 parser.read(file_or_url)
947 fd, path = tempfile.mkstemp(suffix='.cfg')
948 atexit.register(unlink, path)
949 parser.write(os.fdopen(fd, "w"))
950 return path
951
952
953
954
955
956
957 if __name__ == '__main__':
958 import sys
959
960 format = '%(asctime)s %(name)s %(levelname)s %(message)s'
961 logging.basicConfig(level=logging.DEBUG, format=format)
962
963 configFile = sys.argv[1]
964 config = Configuration()
965 config.read(configFile)
966
967 if len(sys.argv) > 2:
968 for var in sys.argv[2:]:
969 (section, option) = var.split(':')
970 val = config.get(section, option, optional=True)
971 print '%s=%s' % (var, val)
972 else:
973 config.write(sys.stdout)
974