1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
6 # Copyright (C) 2010-2012 OpenERP s.a. (<http://openerp.com>).
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU Affero General Public License as
10 # published by the Free Software Foundation, either version 3 of the
11 # License, or (at your option) any later version.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU Affero General Public License for more details.
18 # You should have received a copy of the GNU Affero General Public License
19 # along with this program. If not, see <http://www.gnu.org/licenses/>.
21 ##############################################################################
29 import openerp.loglevels as loglevels
31 import openerp.release as release
33 class MyOption (optparse.Option, object):
34 """ optparse Option with two additional attributes.
36 The list of command line options (getopt.Option) is used to create the
37 list of the configuration file options. When reading the file, and then
38 reading the command line arguments, we don't want optparse.parse results
39 to override the configuration file values. But if we provide default
40 values to optparse, optparse will return them and we can't know if they
41 were really provided by the user or not. A solution is to not use
42 optparse's default attribute, but use a custom one (that will be copied
43 to create the default values of the configuration file).
46 def __init__(self, *opts, **attrs):
47 self.my_default = attrs.pop('my_default', None)
48 super(MyOption, self).__init__(*opts, **attrs)
50 #.apidoc title: Server Configuration Loader
54 from OpenSSL import SSL
57 return hasattr(socket, 'ssl') and hasattr(SSL, "Connection")
61 DEFAULT_LOG_HANDLER = [':INFO']
63 class configmanager(object):
64 def __init__(self, fname=None):
65 # Options not exposed on the command line. Command line options will be added
66 # from optparse's parser.
68 'admin_passwd': 'admin',
69 'csv_internal_sep': ',',
70 'login_message': False,
71 'publisher_warranty_url': 'http://services.openerp.com/publisher-warranty/',
76 # Not exposed in the configuration file.
77 self.blacklist_for_save = set(
78 ['publisher_warranty_url', 'load_language', 'root_path',
79 'init', 'save', 'config', 'update', 'stop_after_init'])
81 # dictionary mapping option destination (keys in self.options) to MyOptions.
85 self.config_file = fname
86 self.has_ssl = check_ssl()
88 self._LOGLEVELS = dict([(getattr(loglevels, 'LOG_%s' % x), getattr(logging, x)) for x in ('CRITICAL', 'ERROR', 'WARNING', 'INFO', 'TEST', 'DEBUG', 'NOTSET')])
90 version = "%s %s" % (release.description, release.version)
91 self.parser = parser = optparse.OptionParser(version=version, option_class=MyOption)
93 # Server startup config
94 group = optparse.OptionGroup(parser, "Common options")
95 group.add_option("-c", "--config", dest="config", help="specify alternate config file")
96 group.add_option("-s", "--save", action="store_true", dest="save", default=False,
97 help="save configuration to ~/.openerp_serverrc")
98 group.add_option("-i", "--init", dest="init", help="install one or more modules (comma-separated list, use \"all\" for all modules), requires -d")
99 group.add_option("-u", "--update", dest="update",
100 help="update one or more modules (comma-separated list, use \"all\" for all modules). Requires -d.")
101 group.add_option("--without-demo", dest="without_demo",
102 help="disable loading demo data for modules to be installed (comma-separated, use \"all\" for all modules). Requires -d and -i. Default is %default",
104 group.add_option("-P", "--import-partial", dest="import_partial", my_default='',
105 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.")
106 group.add_option("--pidfile", dest="pidfile", help="file where the server pid will be stored")
107 group.add_option("--addons-path", dest="addons_path",
108 help="specify additional addons paths (separated by commas).",
109 action="callback", callback=self._check_addons_path, nargs=1, type="string")
110 group.add_option("--load", dest="server_wide_modules", help="Comma-separated list of server-wide modules default=web")
111 parser.add_option_group(group)
114 group = optparse.OptionGroup(parser, "XML-RPC Configuration")
115 group.add_option("--xmlrpc-interface", dest="xmlrpc_interface", my_default='',
116 help="Specify the TCP IP address for the XML-RPC protocol. The empty string binds to all interfaces.")
117 group.add_option("--xmlrpc-port", dest="xmlrpc_port", my_default=8069,
118 help="specify the TCP port for the XML-RPC protocol", type="int")
119 group.add_option("--no-xmlrpc", dest="xmlrpc", action="store_false", my_default=True,
120 help="disable the XML-RPC protocol")
121 group.add_option("--proxy-mode", dest="proxy_mode", action="store_true", my_default=False,
122 help="Enable correct behavior when behind a reverse proxy")
123 parser.add_option_group(group)
126 title = "XML-RPC Secure Configuration"
128 title += " (disabled as ssl is unavailable)"
130 group = optparse.OptionGroup(parser, title)
131 group.add_option("--xmlrpcs-interface", dest="xmlrpcs_interface", my_default='',
132 help="Specify the TCP IP address for the XML-RPC Secure protocol. The empty string binds to all interfaces.")
133 group.add_option("--xmlrpcs-port", dest="xmlrpcs_port", my_default=8071,
134 help="specify the TCP port for the XML-RPC Secure protocol", type="int")
135 group.add_option("--no-xmlrpcs", dest="xmlrpcs", action="store_false", my_default=True,
136 help="disable the XML-RPC Secure protocol")
137 group.add_option("--cert-file", dest="secure_cert_file", my_default='server.cert',
138 help="specify the certificate file for the SSL connection")
139 group.add_option("--pkey-file", dest="secure_pkey_file", my_default='server.pkey',
140 help="specify the private key file for the SSL connection")
141 parser.add_option_group(group)
144 group = optparse.OptionGroup(parser, "NET-RPC Configuration")
145 group.add_option("--netrpc-interface", dest="netrpc_interface", my_default='',
146 help="specify the TCP IP address for the NETRPC protocol")
147 group.add_option("--netrpc-port", dest="netrpc_port", my_default=8070,
148 help="specify the TCP port for the NETRPC protocol", type="int")
149 # Needed a few day for runbot and saas
150 group.add_option("--no-netrpc", dest="netrpc", action="store_false", my_default=False, help="disable the NETRPC protocol")
151 group.add_option("--netrpc", dest="netrpc", action="store_true", my_default=False, help="enable the NETRPC protocol")
152 parser.add_option_group(group)
155 # TODO move to web addons after MetaOption merge
156 group = optparse.OptionGroup(parser, "Web interface Configuration")
157 group.add_option("--db-filter", dest="dbfilter", default='.*',
158 help="Filter listed database", metavar="REGEXP")
159 parser.add_option_group(group)
162 group = optparse.OptionGroup(parser, "Static HTTP service")
163 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")
164 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/')")
165 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 '/')")
166 parser.add_option_group(group)
169 group = optparse.OptionGroup(parser, "Testing Configuration")
170 group.add_option("--test-file", dest="test_file", my_default=False,
171 help="Launch a YML test file.")
172 group.add_option("--test-report-directory", dest="test_report_directory", my_default=False,
173 help="If set, will save sample of all reports in this directory.")
174 group.add_option("--test-enable", action="store_true", dest="test_enable",
175 my_default=False, help="Enable YAML and unit tests.")
176 group.add_option("--test-commit", action="store_true", dest="test_commit",
177 my_default=False, help="Commit database changes performed by YAML or XML tests.")
178 parser.add_option_group(group)
181 group = optparse.OptionGroup(parser, "Logging Configuration")
182 group.add_option("--logfile", dest="logfile", help="file where the server log will be stored")
183 group.add_option("--no-logrotate", dest="logrotate", action="store_false", my_default=True, help="do not rotate the logfile")
184 group.add_option("--syslog", action="store_true", dest="syslog", my_default=False, help="Send the log to the syslog server")
185 group.add_option('--log-handler', action="append", default=DEFAULT_LOG_HANDLER, my_default=DEFAULT_LOG_HANDLER, metavar="PREFIX:LEVEL", help='setup a handler at LEVEL for a given PREFIX. An empty PREFIX indicates the root logger. This option can be repeated. Example: "openerp.orm:DEBUG" or "werkzeug:CRITICAL" (default: ":INFO")')
186 group.add_option('--log-request', action="append_const", dest="log_handler", const="openerp.netsvc.rpc.request:DEBUG", help='shortcut for --log-handler=openerp.netsvc.rpc.request:DEBUG')
187 group.add_option('--log-response', action="append_const", dest="log_handler", const="openerp.netsvc.rpc.response:DEBUG", help='shortcut for --log-handler=openerp.netsvc.rpc.response:DEBUG')
188 group.add_option('--log-web', action="append_const", dest="log_handler", const="openerp.addons.web.http:DEBUG", help='shortcut for --log-handler=openerp.addons.web.http:DEBUG')
189 group.add_option('--log-sql', action="append_const", dest="log_handler", const="openerp.sql_db:DEBUG", help='shortcut for --log-handler=openerp.sql_db:DEBUG')
190 # For backward-compatibility, map the old log levels to something
192 levels = ['info', 'debug_rpc', 'warn', 'test', 'critical',
193 'debug_sql', 'error', 'debug', 'debug_rpc_answer', 'notset']
194 group.add_option('--log-level', dest='log_level', type='choice', choices=levels,
195 my_default='info', help='specify the level of the logging. Accepted values: ' + str(levels) + ' (deprecated option).')
197 parser.add_option_group(group)
200 group = optparse.OptionGroup(parser, "SMTP Configuration")
201 group.add_option('--email-from', dest='email_from', my_default=False,
202 help='specify the SMTP email address for sending email')
203 group.add_option('--smtp', dest='smtp_server', my_default='localhost',
204 help='specify the SMTP server for sending email')
205 group.add_option('--smtp-port', dest='smtp_port', my_default=25,
206 help='specify the SMTP port', type="int")
207 group.add_option('--smtp-ssl', dest='smtp_ssl', action='store_true', my_default=False,
208 help='if passed, SMTP connections will be encrypted with SSL (STARTTLS)')
209 group.add_option('--smtp-user', dest='smtp_user', my_default=False,
210 help='specify the SMTP username for sending email')
211 group.add_option('--smtp-password', dest='smtp_password', my_default=False,
212 help='specify the SMTP password for sending email')
213 parser.add_option_group(group)
215 group = optparse.OptionGroup(parser, "Database related options")
216 group.add_option("-d", "--database", dest="db_name", my_default=False,
217 help="specify the database name")
218 group.add_option("-r", "--db_user", dest="db_user", my_default=False,
219 help="specify the database user name")
220 group.add_option("-w", "--db_password", dest="db_password", my_default=False,
221 help="specify the database password")
222 group.add_option("--pg_path", dest="pg_path", help="specify the pg executable path")
223 group.add_option("--db_host", dest="db_host", my_default=False,
224 help="specify the database host")
225 group.add_option("--db_port", dest="db_port", my_default=False,
226 help="specify the database port", type="int")
227 group.add_option("--db_maxconn", dest="db_maxconn", type='int', my_default=64,
228 help="specify the the maximum number of physical connections to posgresql")
229 group.add_option("--db-template", dest="db_template", my_default="template1",
230 help="specify a custom database template to create a new database")
231 parser.add_option_group(group)
233 group = optparse.OptionGroup(parser, "Internationalisation options",
234 "Use these options to translate OpenERP to another language."
235 "See i18n section of the user manual. Option '-d' is mandatory."
236 "Option '-l' is mandatory in case of importation"
238 group.add_option('--load-language', dest="load_language",
239 help="specifies the languages for the translations you want to be loaded")
240 group.add_option('-l', "--language", dest="language",
241 help="specify the language of the translation file. Use it with --i18n-export or --i18n-import")
242 group.add_option("--i18n-export", dest="translate_out",
243 help="export all sentences to be translated to a CSV file, a PO file or a TGZ archive and exit")
244 group.add_option("--i18n-import", dest="translate_in",
245 help="import a CSV or a PO file with translations and exit. The '-l' option is required.")
246 group.add_option("--i18n-overwrite", dest="overwrite_existing_translations", action="store_true", my_default=False,
247 help="overwrites existing translation terms on updating a module or importing a CSV or a PO file.")
248 group.add_option("--modules", dest="translate_modules",
249 help="specify modules to export. Use in combination with --i18n-export")
250 parser.add_option_group(group)
252 security = optparse.OptionGroup(parser, 'Security-related options')
253 security.add_option('--no-database-list', action="store_false", dest='list_db', my_default=True,
254 help="disable the ability to return the list of databases")
255 parser.add_option_group(security)
258 group = optparse.OptionGroup(parser, "Advanced options")
259 group.add_option('--debug', dest='debug_mode', action='store_true', my_default=False, help='enable debug mode')
260 group.add_option("--stop-after-init", action="store_true", dest="stop_after_init", my_default=False,
261 help="stop the server after its initialization")
262 group.add_option("-t", "--timezone", dest="timezone", my_default=False,
263 help="specify reference timezone for the server (e.g. Europe/Brussels")
264 group.add_option("--osv-memory-count-limit", dest="osv_memory_count_limit", my_default=False,
265 help="Force a limit on the maximum number of records kept in the virtual "
266 "osv_memory tables. The default is False, which means no count-based limit.",
268 group.add_option("--osv-memory-age-limit", dest="osv_memory_age_limit", my_default=1.0,
269 help="Force a limit on the maximum age of records kept in the virtual "
270 "osv_memory tables. This is a decimal value expressed in hours, "
271 "and the default is 1 hour.",
273 group.add_option("--max-cron-threads", dest="max_cron_threads", my_default=2,
274 help="Maximum number of threads processing concurrently cron jobs (default 2).",
276 group.add_option("--unaccent", dest="unaccent", my_default=False, action="store_true",
277 help="Use the unaccent function provided by the database when available.")
278 parser.add_option_group(group)
280 group = optparse.OptionGroup(parser, "Multiprocessing options")
281 # TODO sensible default for the three following limits.
282 group.add_option("--workers", dest="workers", my_default=0,
283 help="Specify the number of workers, 0 disable prefork mode.",
285 group.add_option("--limit-memory-soft", dest="limit_memory_soft", my_default=640 * 1024 * 1024,
286 help="Maximum allowed virtual memory per worker, when reached the worker be reset after the current request (default 671088640 aka 640MB).",
288 group.add_option("--limit-memory-hard", dest="limit_memory_hard", my_default=768 * 1024 * 1024,
289 help="Maximum allowed virtual memory per worker, when reached, any memory allocation will fail (default 805306368 aka 768MB).",
291 group.add_option("--limit-time-cpu", dest="limit_time_cpu", my_default=60,
292 help="Maximum allowed CPU time per request (default 60).",
294 group.add_option("--limit-time-real", dest="limit_time_real", my_default=120,
295 help="Maximum allowed Real time per request (default 120).",
297 group.add_option("--limit-request", dest="limit_request", my_default=8192,
298 help="Maximum number of request to be processed per worker (default 8192).",
300 parser.add_option_group(group)
302 # Copy all optparse options (i.e. MyOption) into self.options.
303 for group in parser.option_groups:
304 for option in group.option_list:
305 if option.dest not in self.options:
306 self.options[option.dest] = option.my_default
307 self.casts[option.dest] = option
309 self.parse_config(None, False)
311 def parse_config(self, args=None, complete=True):
312 """ Parse the configuration file (if any) and the command-line
315 This method initializes openerp.tools.config and openerp.conf (the
316 former should be removed in the furture) with library-wide
317 configuration values.
319 This method must be called before proper usage of this library can be
322 Typical usage of this method:
324 openerp.tools.config.parse_config(sys.argv[1:])
326 :param complete: this is a hack used in __init__(), leave it to True.
331 opt, args = self.parser.parse_args(args)
335 self.parser.error(msg)
337 # Ensures no illegitimate argument is silently discarded (avoids insidious "hyphen to dash" problem)
338 die(args, "unrecognized parameters: '%s'" % " ".join(args))
340 die(bool(opt.syslog) and bool(opt.logfile),
341 "the syslog and logfile options are exclusive")
343 die(opt.translate_in and (not opt.language or not opt.db_name),
344 "the i18n-import option cannot be used without the language (-l) and the database (-d) options")
346 die(opt.overwrite_existing_translations and not (opt.translate_in or opt.update),
347 "the i18n-overwrite option cannot be used without the i18n-import option or without the update option")
349 die(opt.translate_out and (not opt.db_name),
350 "the i18n-export option cannot be used without the database (-d) option")
352 # Check if the config file exists (-c used, but not -s)
353 die(not opt.save and opt.config and not os.path.exists(opt.config),
354 "The config file '%s' selected with -c/--config doesn't exist, "\
355 "use -s/--save if you want to generate it"% opt.config)
357 # place/search the config file on Win32 near the server installation
358 # (../etc from the server)
359 # 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,
360 # else he won't be able to save the configurations, or even to start the server...
362 rcfilepath = os.path.join(os.path.abspath(os.path.dirname(sys.argv[0])), 'openerp-server.conf')
364 rcfilepath = os.path.expanduser('~/.openerp_serverrc')
366 self.rcfile = os.path.abspath(
367 self.config_file or opt.config \
368 or os.environ.get('OPENERP_SERVER') or rcfilepath)
372 # Verify that we want to log or not, if not the output will go to stdout
373 if self.options['logfile'] in ('None', 'False'):
374 self.options['logfile'] = False
375 # the same for the pidfile
376 if self.options['pidfile'] in ('None', 'False'):
377 self.options['pidfile'] = False
379 # if defined dont take the configfile value even if the defined value is None
380 keys = ['xmlrpc_interface', 'xmlrpc_port', 'db_name', 'db_user', 'db_password', 'db_host',
381 'db_port', 'db_template', 'logfile', 'pidfile', 'smtp_port',
382 'email_from', 'smtp_server', 'smtp_user', 'smtp_password',
383 'netrpc_interface', 'netrpc_port', 'db_maxconn', 'import_partial', 'addons_path',
384 'netrpc', 'xmlrpc', 'syslog', 'without_demo', 'timezone',
385 'xmlrpcs_interface', 'xmlrpcs_port', 'xmlrpcs',
386 'static_http_enable', 'static_http_document_root', 'static_http_url_prefix',
387 'secure_cert_file', 'secure_pkey_file', 'dbfilter', 'log_handler', 'log_level'
391 # Copy the command-line argument (except the special case for log_handler, due to
392 # action=append requiring a real default, so we cannot use the my_default workaround)
393 if getattr(opt, arg) and getattr(opt, arg) != DEFAULT_LOG_HANDLER:
394 self.options[arg] = getattr(opt, arg)
395 # ... or keep, but cast, the config file value.
396 elif isinstance(self.options[arg], basestring) and self.casts[arg].type in optparse.Option.TYPE_CHECKER:
397 self.options[arg] = optparse.Option.TYPE_CHECKER[self.casts[arg].type](self.casts[arg], arg, self.options[arg])
400 if isinstance(self.options['log_handler'], basestring):
401 self.options['log_handler'] = self.options['log_handler'].split(',')
403 # if defined but None take the configfile value
405 'language', 'translate_out', 'translate_in', 'overwrite_existing_translations',
406 'debug_mode', 'smtp_ssl', 'load_language',
407 'stop_after_init', 'logrotate', 'without_demo', 'netrpc', 'xmlrpc', 'syslog',
408 'list_db', 'xmlrpcs', 'proxy_mode',
409 'test_file', 'test_enable', 'test_commit', 'test_report_directory',
410 'osv_memory_count_limit', 'osv_memory_age_limit', 'max_cron_threads', 'unaccent',
411 'workers', 'limit_memory_hard', 'limit_memory_soft', 'limit_time_cpu', 'limit_time_real', 'limit_request'
415 # Copy the command-line argument...
416 if getattr(opt, arg) is not None:
417 self.options[arg] = getattr(opt, arg)
418 # ... or keep, but cast, the config file value.
419 elif isinstance(self.options[arg], basestring) and self.casts[arg].type in optparse.Option.TYPE_CHECKER:
420 self.options[arg] = optparse.Option.TYPE_CHECKER[self.casts[arg].type](self.casts[arg], arg, self.options[arg])
422 self.options['root_path'] = os.path.abspath(os.path.expanduser(os.path.expandvars(os.path.dirname(openerp.__file__))))
423 if not self.options['addons_path'] or self.options['addons_path']=='None':
424 self.options['addons_path'] = os.path.join(self.options['root_path'], 'addons')
426 self.options['addons_path'] = ",".join(
427 os.path.abspath(os.path.expanduser(os.path.expandvars(x)))
428 for x in self.options['addons_path'].split(','))
430 self.options['init'] = opt.init and dict.fromkeys(opt.init.split(','), 1) or {}
431 self.options["demo"] = not opt.without_demo and self.options['init'] or {}
432 self.options['update'] = opt.update and dict.fromkeys(opt.update.split(','), 1) or {}
433 self.options['translate_modules'] = opt.translate_modules and map(lambda m: m.strip(), opt.translate_modules.split(',')) or ['all']
434 self.options['translate_modules'].sort()
436 # TODO checking the type of the parameters should be done for every
437 # parameters, not just the timezone.
438 # The call to get_server_timezone() sets the timezone; this should
439 # probably done here.
440 if self.options['timezone']:
441 # Prevent the timezone to be True. (The config file parsing changes
442 # the string 'True' to the boolean value True. It would be probably
443 # be better to remove that conversion.)
444 die(not isinstance(self.options['timezone'], basestring),
445 "Invalid timezone value in configuration or environment: %r.\n"
446 "Please fix this in your configuration." %(self.options['timezone']))
448 # If an explicit TZ was provided in the config, make sure it is known
451 pytz.timezone(self.options['timezone'])
452 except pytz.UnknownTimeZoneError:
453 die(True, "The specified timezone (%s) is invalid" % self.options['timezone'])
455 # If pytz is missing, don't check the provided TZ, it will be ignored anyway.
459 self.options['pg_path'] = opt.pg_path
461 if self.options.get('language', False):
462 if len(self.options['language']) > 5:
463 raise Exception('ERROR: The Lang name must take max 5 chars, Eg: -lfr_BE')
465 if not self.options['db_user']:
468 self.options['db_user'] = getpass.getuser()
470 self.options['db_user'] = None
472 die(not self.options['db_user'], 'ERROR: No user specified for the connection to the database')
474 if self.options['db_password']:
475 if sys.platform == 'win32' and not self.options['db_host']:
476 self.options['db_host'] = 'localhost'
477 #if self.options['db_host']:
478 # self._generate_pgpassfile()
483 openerp.conf.addons_paths = self.options['addons_path'].split(',')
484 if opt.server_wide_modules:
485 openerp.conf.server_wide_modules = map(lambda m: m.strip(), opt.server_wide_modules.split(','))
487 openerp.conf.server_wide_modules = ['web','web_kanban']
489 openerp.modules.module.initialize_sys_path()
490 openerp.modules.loading.open_openerp_namespace()
492 def _generate_pgpassfile(self):
494 Generate the pgpass file with the parameters from the command line (db_host, db_user,
497 Used because pg_dump and pg_restore can not accept the password on the command line.
499 is_win32 = sys.platform == 'win32'
501 filename = os.path.join(os.environ['APPDATA'], 'pgpass.conf')
503 filename = os.path.join(os.environ['HOME'], '.pgpass')
505 text_to_add = "%(db_host)s:*:*:%(db_user)s:%(db_password)s" % self.options
507 if os.path.exists(filename):
508 content = [x.strip() for x in file(filename, 'r').readlines()]
509 if text_to_add in content:
512 fp = file(filename, 'a+')
513 fp.write(text_to_add + "\n")
521 x=_winreg.ConnectRegistry(None,_winreg.HKEY_LOCAL_MACHINE)
522 y = _winreg.OpenKey(x, r"SYSTEM\CurrentControlSet\Control\Session Manager\Environment", 0,_winreg.KEY_ALL_ACCESS)
523 _winreg.SetValueEx(y,"PGPASSFILE", 0, _winreg.REG_EXPAND_SZ, filename )
528 os.chmod(filename, stat.S_IRUSR + stat.S_IWUSR)
530 def _is_addons_path(self, path):
531 for f in os.listdir(path):
532 modpath = os.path.join(path, f)
533 if os.path.isdir(modpath):
534 def hasfile(filename):
535 return os.path.isfile(os.path.join(modpath, filename))
536 if hasfile('__init__.py') and (hasfile('__openerp__.py') or hasfile('__terp__.py')):
540 def _check_addons_path(self, option, opt, value, parser):
542 for path in value.split(','):
544 res = os.path.abspath(os.path.expanduser(path))
545 if not os.path.isdir(res):
546 raise optparse.OptionValueError("option %s: no such directory: %r" % (opt, path))
547 if not self._is_addons_path(res):
548 raise optparse.OptionValueError("option %s: The addons-path %r does not seem to a be a valid Addons Directory!" % (opt, path))
551 setattr(parser.values, option.dest, ",".join(ad_paths))
554 p = ConfigParser.ConfigParser()
556 p.read([self.rcfile])
557 for (name,value) in p.items('options'):
558 if value=='True' or value=='true':
560 if value=='False' or value=='false':
562 self.options[name] = value
563 #parse the other sections, as well
564 for sec in p.sections():
567 if not self.misc.has_key(sec):
569 for (name, value) in p.items(sec):
570 if value=='True' or value=='true':
572 if value=='False' or value=='false':
574 self.misc[sec][name] = value
577 except ConfigParser.NoSectionError:
581 p = ConfigParser.ConfigParser()
582 loglevelnames = dict(zip(self._LOGLEVELS.values(), self._LOGLEVELS.keys()))
583 p.add_section('options')
584 for opt in sorted(self.options.keys()):
585 if opt in ('version', 'language', 'translate_out', 'translate_in', 'overwrite_existing_translations', 'init', 'update'):
587 if opt in self.blacklist_for_save:
589 if opt in ('log_level',):
590 p.set('options', opt, loglevelnames.get(self.options[opt], self.options[opt]))
592 p.set('options', opt, self.options[opt])
594 for sec in sorted(self.misc.keys()):
596 for opt in sorted(self.misc[sec].keys()):
597 p.set(sec,opt,self.misc[sec][opt])
599 # try to create the directories and write the file
601 rc_exists = os.path.exists(self.rcfile)
602 if not rc_exists and not os.path.exists(os.path.dirname(self.rcfile)):
603 os.makedirs(os.path.dirname(self.rcfile))
605 p.write(file(self.rcfile, 'w'))
607 os.chmod(self.rcfile, 0600)
609 sys.stderr.write("ERROR: couldn't write the config file\n")
612 # what to do if impossible?
613 sys.stderr.write("ERROR: couldn't create the config directory\n")
615 def get(self, key, default=None):
616 return self.options.get(key, default)
618 def get_misc(self, sect, key, default=None):
619 return self.misc.get(sect,{}).get(key, default)
621 def __setitem__(self, key, value):
622 self.options[key] = value
623 if key in self.options and isinstance(self.options[key], basestring) and \
624 key in self.casts and self.casts[key].type in optparse.Option.TYPE_CHECKER:
625 self.options[key] = optparse.Option.TYPE_CHECKER[self.casts[key].type](self.casts[key], key, self.options[key])
627 def __getitem__(self, key):
628 return self.options[key]
630 config = configmanager()
633 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: