1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 ##############################################################################
28 import openerp.loglevels as loglevels
30 import openerp.release as release
32 class MyOption (optparse.Option, object):
33 """ optparse Option with two additional attributes.
35 The list of command line options (getopt.Option) is used to create the
36 list of the configuration file options. When reading the file, and then
37 reading the command line arguments, we don't want optparse.parse results
38 to override the configuration file values. But if we provide default
39 values to optparse, optparse will return them and we can't know if they
40 were really provided by the user or not. A solution is to not use
41 optparse's default attribute, but use a custom one (that will be copied
42 to create the default values of the configuration file).
45 def __init__(self, *opts, **attrs):
46 self.my_default = attrs.pop('my_default', None)
47 super(MyOption, self).__init__(*opts, **attrs)
49 #.apidoc title: Server Configuration Loader
53 from OpenSSL import SSL
56 return hasattr(socket, 'ssl') and hasattr(SSL, "Connection")
60 class configmanager(object):
61 def __init__(self, fname=None):
62 # Options not exposed on the command line. Command line options will be added
63 # from optparse's parser.
65 'admin_passwd': 'admin',
66 'csv_internal_sep': ',',
67 'login_message': False,
68 'publisher_warranty_url': 'http://services.openerp.com/publisher-warranty/',
73 # Not exposed in the configuration file.
74 self.blacklist_for_save = set(
75 ['publisher_warranty_url', 'load_language', 'root_path',
76 'init', 'save', 'config', 'update'])
78 # dictionary mapping option destination (keys in self.options) to MyOptions.
82 self.config_file = fname
83 self.has_ssl = check_ssl()
85 self._LOGLEVELS = dict([(getattr(loglevels, 'LOG_%s' % x), getattr(logging, x))
86 for x in ('CRITICAL', 'ERROR', 'WARNING', 'INFO', 'TEST', 'DEBUG', 'DEBUG_RPC', 'DEBUG_SQL', 'DEBUG_RPC_ANSWER','NOTSET')])
88 version = "%s %s" % (release.description, release.version)
89 self.parser = parser = optparse.OptionParser(version=version, option_class=MyOption)
91 # Server startup config
92 group = optparse.OptionGroup(parser, "Common options")
93 group.add_option("-c", "--config", dest="config", help="specify alternate config file")
94 group.add_option("-s", "--save", action="store_true", dest="save", default=False,
95 help="save configuration to ~/.openerp_serverrc")
96 group.add_option("-i", "--init", dest="init", help="install one or more modules (comma-separated list, use \"all\" for all modules), requires -d")
97 group.add_option("-u", "--update", dest="update",
98 help="update one or more modules (comma-separated list, use \"all\" for all modules). Requires -d.")
99 group.add_option("--without-demo", dest="without_demo",
100 help="disable loading demo data for modules to be installed (comma-separated, use \"all\" for all modules). Requires -d and -i. Default is %default",
102 group.add_option("-P", "--import-partial", dest="import_partial", my_default='',
103 help="Use this for big data importation, if it crashes you will be able to continue at the current state. Provide a filename to store intermediate importation states.")
104 group.add_option("--pidfile", dest="pidfile", help="file where the server pid will be stored")
105 group.add_option("--load", dest="server_wide_modules", help="Comma-separated list of server-wide modules")
106 parser.add_option_group(group)
109 group = optparse.OptionGroup(parser, "XML-RPC Configuration")
110 group.add_option("--xmlrpc-interface", dest="xmlrpc_interface", my_default='',
111 help="Specify the TCP IP address for the XML-RPC protocol. The empty string binds to all interfaces.")
112 group.add_option("--xmlrpc-port", dest="xmlrpc_port", my_default=8069,
113 help="specify the TCP port for the XML-RPC protocol", type="int")
114 group.add_option("--no-xmlrpc", dest="xmlrpc", action="store_false", my_default=True,
115 help="disable the XML-RPC protocol")
116 parser.add_option_group(group)
119 title = "XML-RPC Secure Configuration"
121 title += " (disabled as ssl is unavailable)"
123 group = optparse.OptionGroup(parser, title)
124 group.add_option("--xmlrpcs-interface", dest="xmlrpcs_interface", my_default='',
125 help="Specify the TCP IP address for the XML-RPC Secure protocol. The empty string binds to all interfaces.")
126 group.add_option("--xmlrpcs-port", dest="xmlrpcs_port", my_default=8071,
127 help="specify the TCP port for the XML-RPC Secure protocol", type="int")
128 group.add_option("--no-xmlrpcs", dest="xmlrpcs", action="store_false", my_default=True,
129 help="disable the XML-RPC Secure protocol")
130 group.add_option("--cert-file", dest="secure_cert_file", my_default='server.cert',
131 help="specify the certificate file for the SSL connection")
132 group.add_option("--pkey-file", dest="secure_pkey_file", my_default='server.pkey',
133 help="specify the private key file for the SSL connection")
134 parser.add_option_group(group)
137 group = optparse.OptionGroup(parser, "NET-RPC Configuration")
138 group.add_option("--netrpc-interface", dest="netrpc_interface", my_default='',
139 help="specify the TCP IP address for the NETRPC protocol")
140 group.add_option("--netrpc-port", dest="netrpc_port", my_default=8070,
141 help="specify the TCP port for the NETRPC protocol", type="int")
142 group.add_option("--no-netrpc", dest="netrpc", action="store_false", my_default=True,
143 help="disable the NETRPC protocol")
144 parser.add_option_group(group)
147 # TODO move to web addons after MetaOption merge
148 group = optparse.OptionGroup(parser, "Web interface Configuration")
149 group.add_option("--db-filter", dest="dbfilter", default='.*',
150 help="Filter listed database", metavar="REGEXP")
151 parser.add_option_group(group)
154 group = optparse.OptionGroup(parser, "Static HTTP service")
155 group.add_option("--static-http-enable", dest="static_http_enable", action="store_true", my_default=False, help="enable static HTTP service for serving plain HTML files")
156 group.add_option("--static-http-document-root", dest="static_http_document_root", help="specify the directory containing your static HTML files (e.g '/var/www/')")
157 group.add_option("--static-http-url-prefix", dest="static_http_url_prefix", help="specify the URL root prefix where you want web browsers to access your static HTML files (e.g '/')")
158 parser.add_option_group(group)
161 group = optparse.OptionGroup(parser, "Testing Configuration")
162 group.add_option("--test-file", dest="test_file", my_default=False,
163 help="Launch a YML test file.")
164 group.add_option("--test-report-directory", dest="test_report_directory", my_default=False,
165 help="If set, will save sample of all reports in this directory.")
166 group.add_option("--test-disable", action="store_true", dest="test_disable",
167 my_default=False, help="Disable loading test files.")
168 group.add_option("--test-commit", action="store_true", dest="test_commit",
169 my_default=False, help="Commit database changes performed by tests.")
170 group.add_option("--assert-exit-level", dest='assert_exit_level', type="choice", choices=self._LOGLEVELS.keys(),
172 help="specify the level at which a failed assertion will stop the server. Accepted values: %s" % (self._LOGLEVELS.keys(),))
173 parser.add_option_group(group)
176 group = optparse.OptionGroup(parser, "Logging Configuration")
177 group.add_option("--logfile", dest="logfile", help="file where the server log will be stored")
178 group.add_option("--no-logrotate", dest="logrotate", action="store_false", my_default=True,
179 help="do not rotate the logfile")
180 group.add_option("--syslog", action="store_true", dest="syslog",
181 my_default=False, help="Send the log to the syslog server")
182 group.add_option('--log-level', dest='log_level', type='choice', choices=self._LOGLEVELS.keys(),
184 help='specify the level of the logging. Accepted values: ' + str(self._LOGLEVELS.keys()))
185 parser.add_option_group(group)
188 group = optparse.OptionGroup(parser, "SMTP Configuration")
189 group.add_option('--email-from', dest='email_from', my_default=False,
190 help='specify the SMTP email address for sending email')
191 group.add_option('--smtp', dest='smtp_server', my_default='localhost',
192 help='specify the SMTP server for sending email')
193 group.add_option('--smtp-port', dest='smtp_port', my_default=25,
194 help='specify the SMTP port', type="int")
195 group.add_option('--smtp-ssl', dest='smtp_ssl', action='store_true', my_default=False,
196 help='specify the SMTP server support SSL or not')
197 group.add_option('--smtp-user', dest='smtp_user', my_default=False,
198 help='specify the SMTP username for sending email')
199 group.add_option('--smtp-password', dest='smtp_password', my_default=False,
200 help='specify the SMTP password for sending email')
201 parser.add_option_group(group)
203 group = optparse.OptionGroup(parser, "Database related options")
204 group.add_option("-d", "--database", dest="db_name", my_default=False,
205 help="specify the database name")
206 group.add_option("-r", "--db_user", dest="db_user", my_default=False,
207 help="specify the database user name")
208 group.add_option("-w", "--db_password", dest="db_password", my_default=False,
209 help="specify the database password")
210 group.add_option("--pg_path", dest="pg_path", help="specify the pg executable path")
211 group.add_option("--db_host", dest="db_host", my_default=False,
212 help="specify the database host")
213 group.add_option("--db_port", dest="db_port", my_default=False,
214 help="specify the database port", type="int")
215 group.add_option("--db_maxconn", dest="db_maxconn", type='int', my_default=64,
216 help="specify the the maximum number of physical connections to posgresql")
217 parser.add_option_group(group)
219 group = optparse.OptionGroup(parser, "Internationalisation options",
220 "Use these options to translate OpenERP to another language."
221 "See i18n section of the user manual. Option '-d' is mandatory."
222 "Option '-l' is mandatory in case of importation"
224 group.add_option('--load-language', dest="load_language",
225 help="specifies the languages for the translations you want to be loaded")
226 group.add_option('-l', "--language", dest="language",
227 help="specify the language of the translation file. Use it with --i18n-export or --i18n-import")
228 group.add_option("--i18n-export", dest="translate_out",
229 help="export all sentences to be translated to a CSV file, a PO file or a TGZ archive and exit")
230 group.add_option("--i18n-import", dest="translate_in",
231 help="import a CSV or a PO file with translations and exit. The '-l' option is required.")
232 group.add_option("--i18n-overwrite", dest="overwrite_existing_translations", action="store_true", my_default=False,
233 help="overwrites existing translation terms on updating a module or importing a CSV or a PO file.")
234 group.add_option("--modules", dest="translate_modules",
235 help="specify modules to export. Use in combination with --i18n-export")
236 group.add_option("--addons-path", dest="addons_path",
237 help="specify additional addons paths (separated by commas).",
238 action="callback", callback=self._check_addons_path, nargs=1, type="string")
239 parser.add_option_group(group)
241 security = optparse.OptionGroup(parser, 'Security-related options')
242 security.add_option('--no-database-list', action="store_false", dest='list_db', my_default=True,
243 help="disable the ability to return the list of databases")
244 parser.add_option_group(security)
247 group = optparse.OptionGroup(parser, "Advanced options")
248 group.add_option("--cache-timeout", dest="cache_timeout", my_default=100000,
249 help="set the timeout for the cache system", type="int")
250 group.add_option('--debug', dest='debug_mode', action='store_true', my_default=False, help='enable debug mode')
251 group.add_option("--stop-after-init", action="store_true", dest="stop_after_init", my_default=False,
252 help="stop the server after its initialization")
253 group.add_option("-t", "--timezone", dest="timezone", my_default=False,
254 help="specify reference timezone for the server (e.g. Europe/Brussels")
255 group.add_option("--osv-memory-count-limit", dest="osv_memory_count_limit", my_default=False,
256 help="Force a limit on the maximum number of records kept in the virtual "
257 "osv_memory tables. The default is False, which means no count-based limit.",
259 group.add_option("--osv-memory-age-limit", dest="osv_memory_age_limit", my_default=1.0,
260 help="Force a limit on the maximum age of records kept in the virtual "
261 "osv_memory tables. This is a decimal value expressed in hours, "
262 "and the default is 1 hour.",
264 group.add_option("--max-cron-threads", dest="max_cron_threads", my_default=4,
265 help="Maximum number of threads processing concurrently cron jobs.",
267 group.add_option("--unaccent", dest="unaccent", my_default=False, action="store_true",
268 help="Use the unaccent function provided by the database when available.")
270 parser.add_option_group(group)
272 # Copy all optparse options (i.e. MyOption) into self.options.
273 for group in parser.option_groups:
274 for option in group.option_list:
275 self.options[option.dest] = option.my_default
276 self.casts[option.dest] = option
278 self.parse_config(None, False)
280 def parse_config(self, args=None, complete=True):
281 """ Parse the configuration file (if any) and the command-line
284 This method initializes openerp.tools.config and openerp.conf (the
285 former should be removed in the furture) with library-wide
286 configuration values.
288 This method must be called before proper usage of this library can be
291 Typical usage of this method:
293 openerp.tools.config.parse_config(sys.argv[1:])
295 :param complete: this is a hack used in __init__(), leave it to True.
300 opt, args = self.parser.parse_args(args)
304 self.parser.error(msg)
306 # Ensures no illegitimate argument is silently discarded (avoids insidious "hyphen to dash" problem)
307 die(args, "unrecognized parameters: '%s'" % " ".join(args))
309 die(bool(opt.syslog) and bool(opt.logfile),
310 "the syslog and logfile options are exclusive")
312 die(opt.translate_in and (not opt.language or not opt.db_name),
313 "the i18n-import option cannot be used without the language (-l) and the database (-d) options")
315 die(opt.overwrite_existing_translations and not (opt.translate_in or opt.update),
316 "the i18n-overwrite option cannot be used without the i18n-import option or without the update option")
318 die(opt.translate_out and (not opt.db_name),
319 "the i18n-export option cannot be used without the database (-d) option")
321 # Check if the config file exists (-c used, but not -s)
322 die(not opt.save and opt.config and not os.path.exists(opt.config),
323 "The config file '%s' selected with -c/--config doesn't exist, "\
324 "use -s/--save if you want to generate it"%(opt.config))
326 # place/search the config file on Win32 near the server installation
327 # (../etc from the server)
328 # if the server is run by an unprivileged user, he has to specify location of a config file where he has the rights to write,
329 # else he won't be able to save the configurations, or even to start the server...
331 rcfilepath = os.path.join(os.path.abspath(os.path.dirname(sys.argv[0])), 'openerp-server.conf')
333 rcfilepath = os.path.expanduser('~/.openerp_serverrc')
335 self.rcfile = os.path.abspath(
336 self.config_file or opt.config \
337 or os.environ.get('OPENERP_SERVER') or rcfilepath)
341 # Verify that we want to log or not, if not the output will go to stdout
342 if self.options['logfile'] in ('None', 'False'):
343 self.options['logfile'] = False
344 # the same for the pidfile
345 if self.options['pidfile'] in ('None', 'False'):
346 self.options['pidfile'] = False
348 keys = ['xmlrpc_interface', 'xmlrpc_port', 'db_name', 'db_user', 'db_password', 'db_host',
349 'db_port', 'logfile', 'pidfile', 'smtp_port', 'cache_timeout',
350 'email_from', 'smtp_server', 'smtp_user', 'smtp_password',
351 'netrpc_interface', 'netrpc_port', 'db_maxconn', 'import_partial', 'addons_path',
352 'netrpc', 'xmlrpc', 'syslog', 'without_demo', 'timezone',
353 'xmlrpcs_interface', 'xmlrpcs_port', 'xmlrpcs',
354 'static_http_enable', 'static_http_document_root', 'static_http_url_prefix',
355 'secure_cert_file', 'secure_pkey_file', 'dbfilter'
359 # Copy the command-line argument...
360 if getattr(opt, arg):
361 self.options[arg] = getattr(opt, arg)
362 # ... or keep, but cast, the config file value.
363 elif isinstance(self.options[arg], basestring) and self.casts[arg].type in optparse.Option.TYPE_CHECKER:
364 self.options[arg] = optparse.Option.TYPE_CHECKER[self.casts[arg].type](self.casts[arg], arg, self.options[arg])
367 'language', 'translate_out', 'translate_in', 'overwrite_existing_translations',
368 'debug_mode', 'smtp_ssl', 'load_language',
369 'stop_after_init', 'logrotate', 'without_demo', 'netrpc', 'xmlrpc', 'syslog',
370 'list_db', 'xmlrpcs',
371 'test_file', 'test_disable', 'test_commit', 'test_report_directory',
372 'osv_memory_count_limit', 'osv_memory_age_limit', 'max_cron_threads', 'unaccent',
376 # Copy the command-line argument...
377 if getattr(opt, arg) is not None:
378 self.options[arg] = getattr(opt, arg)
379 # ... or keep, but cast, the config file value.
380 elif isinstance(self.options[arg], basestring) and self.casts[arg].type in optparse.Option.TYPE_CHECKER:
381 self.options[arg] = optparse.Option.TYPE_CHECKER[self.casts[arg].type](self.casts[arg], arg, self.options[arg])
383 if opt.assert_exit_level:
384 self.options['assert_exit_level'] = self._LOGLEVELS[opt.assert_exit_level]
386 self.options['assert_exit_level'] = self._LOGLEVELS.get(self.options['assert_exit_level']) or int(self.options['assert_exit_level'])
389 self.options['log_level'] = self._LOGLEVELS[opt.log_level]
391 self.options['log_level'] = self._LOGLEVELS.get(self.options['log_level']) or int(self.options['log_level'])
393 self.options['root_path'] = os.path.abspath(os.path.expanduser(os.path.expandvars(os.path.dirname(openerp.__file__))))
394 if not self.options['addons_path'] or self.options['addons_path']=='None':
395 self.options['addons_path'] = os.path.join(self.options['root_path'], 'addons')
397 self.options['addons_path'] = ",".join(
398 os.path.abspath(os.path.expanduser(os.path.expandvars(x)))
399 for x in self.options['addons_path'].split(','))
401 self.options['init'] = opt.init and dict.fromkeys(opt.init.split(','), 1) or {}
402 self.options["demo"] = not opt.without_demo and self.options['init'] or {}
403 self.options['update'] = opt.update and dict.fromkeys(opt.update.split(','), 1) or {}
404 self.options['translate_modules'] = opt.translate_modules and map(lambda m: m.strip(), opt.translate_modules.split(',')) or ['all']
405 self.options['translate_modules'].sort()
407 # TODO checking the type of the parameters should be done for every
408 # parameters, not just the timezone.
409 # The call to get_server_timezone() sets the timezone; this should
410 # probably done here.
411 if self.options['timezone']:
412 # Prevent the timezone to be True. (The config file parsing changes
413 # the string 'True' to the boolean value True. It would be probably
414 # be better to remove that conversion.)
415 die(not isinstance(self.options['timezone'], basestring),
416 "Invalid timezone value in configuration or environment: %r.\n"
417 "Please fix this in your configuration." %(self.options['timezone']))
419 # If an explicit TZ was provided in the config, make sure it is known
422 pytz.timezone(self.options['timezone'])
423 except pytz.UnknownTimeZoneError:
424 die(True, "The specified timezone (%s) is invalid" % self.options['timezone'])
426 # If pytz is missing, don't check the provided TZ, it will be ignored anyway.
430 self.options['pg_path'] = opt.pg_path
432 if self.options.get('language', False):
433 if len(self.options['language']) > 5:
434 raise Exception('ERROR: The Lang name must take max 5 chars, Eg: -lfr_BE')
436 if not self.options['db_user']:
439 self.options['db_user'] = getpass.getuser()
441 self.options['db_user'] = None
443 die(not self.options['db_user'], 'ERROR: No user specified for the connection to the database')
445 if self.options['db_password']:
446 if sys.platform == 'win32' and not self.options['db_host']:
447 self.options['db_host'] = 'localhost'
448 #if self.options['db_host']:
449 # self._generate_pgpassfile()
454 openerp.conf.max_cron_threads = self.options['max_cron_threads']
456 openerp.conf.addons_paths = self.options['addons_path'].split(',')
457 openerp.conf.server_wide_modules = \
458 map(lambda m: m.strip(), opt.server_wide_modules.split(',')) if \
459 opt.server_wide_modules else []
461 openerp.modules.module.initialize_sys_path()
462 openerp.modules.loading.open_openerp_namespace()
463 # openerp.addons.__path__.extend(openerp.conf.addons_paths) # This
464 # is not compatible with initialize_sys_path(): import crm and
465 # import openerp.addons.crm load twice the module.
467 def _generate_pgpassfile(self):
469 Generate the pgpass file with the parameters from the command line (db_host, db_user,
472 Used because pg_dump and pg_restore can not accept the password on the command line.
474 is_win32 = sys.platform == 'win32'
476 filename = os.path.join(os.environ['APPDATA'], 'pgpass.conf')
478 filename = os.path.join(os.environ['HOME'], '.pgpass')
480 text_to_add = "%(db_host)s:*:*:%(db_user)s:%(db_password)s" % self.options
482 if os.path.exists(filename):
483 content = [x.strip() for x in file(filename, 'r').readlines()]
484 if text_to_add in content:
487 fp = file(filename, 'a+')
488 fp.write(text_to_add + "\n")
496 x=_winreg.ConnectRegistry(None,_winreg.HKEY_LOCAL_MACHINE)
497 y = _winreg.OpenKey(x, r"SYSTEM\CurrentControlSet\Control\Session Manager\Environment", 0,_winreg.KEY_ALL_ACCESS)
498 _winreg.SetValueEx(y,"PGPASSFILE", 0, _winreg.REG_EXPAND_SZ, filename )
503 os.chmod(filename, stat.S_IRUSR + stat.S_IWUSR)
505 def _is_addons_path(self, path):
506 for f in os.listdir(path):
507 modpath = os.path.join(path, f)
508 if os.path.isdir(modpath):
509 def hasfile(filename):
510 return os.path.isfile(os.path.join(modpath, filename))
511 if hasfile('__init__.py') and (hasfile('__openerp__.py') or hasfile('__terp__.py')):
515 def _check_addons_path(self, option, opt, value, parser):
517 for path in value.split(','):
519 res = os.path.abspath(os.path.expanduser(path))
520 if not os.path.isdir(res):
521 raise optparse.OptionValueError("option %s: no such directory: %r" % (opt, path))
522 if not self._is_addons_path(res):
523 raise optparse.OptionValueError("option %s: The addons-path %r does not seem to a be a valid Addons Directory!" % (opt, path))
526 setattr(parser.values, option.dest, ",".join(ad_paths))
529 p = ConfigParser.ConfigParser()
531 p.read([self.rcfile])
532 for (name,value) in p.items('options'):
533 if value=='True' or value=='true':
535 if value=='False' or value=='false':
537 self.options[name] = value
538 #parse the other sections, as well
539 for sec in p.sections():
542 if not self.misc.has_key(sec):
544 for (name, value) in p.items(sec):
545 if value=='True' or value=='true':
547 if value=='False' or value=='false':
549 self.misc[sec][name] = value
552 except ConfigParser.NoSectionError:
556 p = ConfigParser.ConfigParser()
557 loglevelnames = dict(zip(self._LOGLEVELS.values(), self._LOGLEVELS.keys()))
558 p.add_section('options')
559 for opt in sorted(self.options.keys()):
560 if opt in ('version', 'language', 'translate_out', 'translate_in', 'overwrite_existing_translations', 'init', 'update'):
562 if opt in self.blacklist_for_save:
564 if opt in ('log_level', 'assert_exit_level'):
565 p.set('options', opt, loglevelnames.get(self.options[opt], self.options[opt]))
567 p.set('options', opt, self.options[opt])
569 for sec in sorted(self.misc.keys()):
570 for opt in sorted(self.misc[sec].keys()):
571 p.set(sec,opt,self.misc[sec][opt])
573 # try to create the directories and write the file
575 rc_exists = os.path.exists(self.rcfile)
576 if not rc_exists and not os.path.exists(os.path.dirname(self.rcfile)):
577 os.makedirs(os.path.dirname(self.rcfile))
579 p.write(file(self.rcfile, 'w'))
581 os.chmod(self.rcfile, 0600)
583 sys.stderr.write("ERROR: couldn't write the config file\n")
586 # what to do if impossible?
587 sys.stderr.write("ERROR: couldn't create the config directory\n")
589 def get(self, key, default=None):
590 return self.options.get(key, default)
592 def get_misc(self, sect, key, default=None):
593 return self.misc.get(sect,{}).get(key, default)
595 def __setitem__(self, key, value):
596 self.options[key] = value
598 def __getitem__(self, key):
599 return self.options[key]
601 config = configmanager()