Re-flow indentation of bugfix items.
[tor-bridgedb.git] / bridgedb / configure.py
blobab8e08ab68d5b52dcff31c11b6c24c7a34ac41a9
1 # -*- coding: utf-8 ; test-case-name: bridgedb.test.test_configure -*-
3 # This file is part of BridgeDB, a Tor bridge distribution system.
5 # :authors: please see the AUTHORS file for attributions
6 # :copyright: (c) 2013-2015, Isis Lovecruft
7 # (c) 2013-2015, Matthew Finkel
8 # (c) 2007-2015, Nick Mathewson
9 # (c) 2007-2015, The Tor Project, Inc.
10 # :license: see LICENSE for licensing information
12 """Utilities for dealing with configuration files for BridgeDB."""
14 import logging
15 import os
17 # Used to set the SUPPORTED_TRANSPORTS:
18 from bridgedb import strings
21 def loadConfig(configFile=None, configCls=None):
22 """Load configuration settings on top of the current settings.
24 All pathnames and filenames within settings in the ``configFile`` will be
25 expanded, and their expanded values will be stored in the returned
26 :class:`configuration <bridgedb.configure.Conf>` object.
28 **Note:**
30 On the strange-looking use of::
32 exec compile(open(configFile).read(), '<string>', 'exec') in dict()
34 in this function…
36 The contents of the config file should be compiled first, and then passed
37 to ``exec()`` -- not ``execfile()`` ! -- in order to get the contents of
38 the config file to exist within the scope of the configuration dictionary.
39 Otherwise, Python *will* default_ to executing the config file directly
40 within the ``globals()`` scope.
42 Additionally, it's roughly 20-30 times faster_ to use the ``compile()``
43 builtin on a string (the contents of the file) before passing it to
44 ``exec()``, than using ``execfile()`` directly on the file.
46 .. _default: http://stackoverflow.com/q/17470193
47 .. _faster: http://lucumr.pocoo.org/2011/2/1/exec-in-python/
49 :ivar bool itsSafeToUseLogging: This is called in
50 :func:`bridgedb.main.run` before
51 :func:`bridgedb.safelog.configureLogging`. When called from
52 :func:`~bridgedb.main.run`, the **configCls** parameter is not given,
53 because that is the first time that a
54 :class:`config <bridgedb.configure.Conf>` has been created. If a
55 :class:`logging.Logger` is created in this function, then logging will
56 not be correctly configured, therefore, if the **configCls** parameter
57 is not given, then it's the first time this function has been called
58 and it is therefore *not* safe to make calls to the logging module.
59 :type configFile: :any:`str` or ``None``
60 :param configFile: If given, the filename of the config file to load.
61 :type configCls: :class:`bridgedb.configure.Conf` or ``None``
62 :param configCls: The current configuration instance, if one already
63 exists.
64 :returns: A new :class:`configuration <bridgedb.configure.Conf>`, with the
65 old settings as defaults, and the settings from the **configFile** (if
66 given) overriding those defaults.
67 """
68 itsSafeToUseLogging = False
69 configuration = {}
71 if configCls:
72 itsSafeToUseLogging = True
73 oldConfig = configCls.__dict__
74 configuration.update(**oldConfig) # Load current settings
75 logging.info("Reloading over in-memory configurations...")
77 conffile = configFile
78 if (configFile is None) and ('CONFIG_FILE' in configuration):
79 conffile = configuration['CONFIG_FILE']
81 if conffile is not None:
82 if itsSafeToUseLogging:
83 logging.info("Loading settings from config file: '%s'" % conffile)
84 compiled = compile(open(conffile).read(), '<string>', 'exec')
85 exec(compiled, configuration)
87 if itsSafeToUseLogging:
88 logging.debug("New configuration settings:")
89 logging.debug("\n".join(["{0} = {1}".format(key, value)
90 for key, value in configuration.items()
91 if not key.startswith('_')]))
93 # Create a :class:`Conf` from the settings stored within the local scope
94 # of the ``configuration`` dictionary:
95 config = Conf(**configuration)
97 # We want to set the updated/expanded paths for files on the ``config``,
98 # because the copy of this config, `state.config` is used later to compare
99 # with a new :class:`Conf` instance, to see if there were any changes.
101 # See :meth:`bridgedb.persistent.State.useUpdatedSettings`.
103 for attr in ["PROXY_LIST_FILES"]:
104 setting = getattr(config, attr, None)
105 if setting is None: # pragma: no cover
106 setattr(config, attr, []) # If they weren't set, make them lists
107 else:
108 setattr(config, attr, # If they were set, expand the paths:
109 [os.path.abspath(os.path.expanduser(f)) for f in setting])
111 for attr in ["DB_FILE", "DB_LOG_FILE", "MASTER_KEY_FILE", "PIDFILE",
112 "ASSIGNMENTS_FILE", "HTTPS_CERT_FILE", "HTTPS_KEY_FILE",
113 "MOAT_CERT_FILE", "MOAT_KEY_FILE",
114 "LOG_FILE", "COUNTRY_BLOCK_FILE",
115 "GIMP_CAPTCHA_DIR", "GIMP_CAPTCHA_HMAC_KEYFILE",
116 "GIMP_CAPTCHA_RSA_KEYFILE", "NO_DISTRIBUTION_FILE"]:
117 setting = getattr(config, attr, None)
118 if setting is None:
119 setattr(config, attr, setting)
120 else:
121 setattr(config, attr, os.path.abspath(os.path.expanduser(setting)))
123 for attr in ["MOAT_ROTATION_PERIOD",
124 "HTTPS_ROTATION_PERIOD",
125 "EMAIL_ROTATION_PERIOD"]:
126 setting = getattr(config, attr, None) # Default to None
127 setattr(config, attr, setting)
129 for attr in ["IGNORE_NETWORKSTATUS",
130 "MOAT_CSP_ENABLED",
131 "MOAT_CSP_REPORT_ONLY",
132 "MOAT_CSP_INCLUDE_SELF",
133 "CSP_ENABLED",
134 "CSP_REPORT_ONLY",
135 "CSP_INCLUDE_SELF"]:
136 setting = getattr(config, attr, True) # Default to True
137 setattr(config, attr, setting)
139 for attr in ["FORCE_PORTS", "FORCE_FLAGS", "NO_DISTRIBUTION_COUNTRIES"]:
140 setting = getattr(config, attr, []) # Default to empty lists
141 setattr(config, attr, setting)
143 for attr in ["SUPPORTED_TRANSPORTS"]:
144 setting = getattr(config, attr, {}) # Default to empty dicts
145 setattr(config, attr, setting)
147 # Set the SUPPORTED_TRANSPORTS to populate the webserver and email options:
148 strings._setSupportedTransports(getattr(config, "SUPPORTED_TRANSPORTS", {}))
149 strings._setDefaultTransport(getattr(config, "DEFAULT_TRANSPORT", ""))
150 logging.info("Currently supported transports: %s" %
151 " ".join(strings._getSupportedTransports()))
152 logging.info("Default transport: %s" % strings._getDefaultTransport())
154 for domain in config.EMAIL_DOMAINS:
155 config.EMAIL_DOMAIN_MAP[domain] = domain
157 if conffile: # Store the pathname of the config file, if one was used
158 config.CONFIG_FILE = os.path.abspath(os.path.expanduser(conffile))
160 return config
163 class Conf(object):
164 """A configuration object. Holds unvalidated attributes."""
165 def __init__(self, **attrs):
166 for key, value in attrs.items():
167 if key == key.upper():
168 if not key.startswith('__'):
169 self.__dict__[key] = value