2138
2138
class Store(object):
2139
2139
"""Abstract interface to persistent storage for configuration options."""
2141
readonly_section_class = ReadOnlySection
2142
mutable_section_class = MutableSection
2146
raise NotImplementedError(self.loaded)
2144
2148
def load(self):
2145
2149
raise NotImplementedError(self.load)
2151
def _load_from_string(self, str_or_unicode):
2152
"""Create a store from a string in configobj syntax.
2154
:param str_or_unicode: A string representing the file content. This will
2155
be encoded to suit store needs internally.
2157
This is for tests and should not be used in production unless a
2158
convincing use case can be demonstrated :)
2160
raise NotImplementedError(self._load_from_string)
2147
2162
def save(self):
2148
2163
raise NotImplementedError(self.save)
2165
def external_url(self):
2166
raise NotImplementedError(self.external_url)
2150
2168
def get_sections(self):
2151
2169
"""Returns an ordered iterable of existing sections.
2174
2192
super(ConfigObjStore, self).__init__()
2175
2193
self.transport = transport
2176
2194
self.file_name = file_name
2177
# No transient content is known initially
2178
self._content = None
2181
def from_string(cls, str_or_unicode, transport, file_name):
2195
self._config_obj = None
2199
return self._config_obj != None
2202
"""Load the store from the associated file."""
2205
content = self.transport.get_bytes(self.file_name)
2206
self._load_from_string(content)
2208
def _load_from_string(self, str_or_unicode):
2182
2209
"""Create a config store from a string.
2184
2211
:param str_or_unicode: A string representing the file content. This will
2185
2212
be utf-8 encoded internally.
2187
:param transport: The transport object where the config file is located.
2189
:param file_name: The configuration file basename.
2191
conf = cls(transport=transport, file_name=file_name)
2192
conf._create_from_string(str_or_unicode)
2195
def _create_from_string(self, str_or_unicode):
2196
# We just keep the content waiting for load() to be called when needed
2197
self._content = StringIO(str_or_unicode.encode('utf-8'))
2199
def load(self, allow_no_such_file=False):
2200
"""Load the store from the associated file.
2202
:param allow_no_such_file: Swallow the NoSuchFile exception if True.
2203
This allows delayed loading when creating the first option ever.
2214
This is for tests and should not be used in production unless a
2215
convincing use case can be demonstrated :)
2205
2217
if self.loaded:
2207
if self._content is not None:
2208
co_input = self._content
2211
content = self.transport.get_bytes(self.file_name)
2212
except errors.NoSuchFile:
2213
if allow_no_such_file:
2217
co_input = StringIO(content)
2218
raise AssertionError('Already loaded: %r' % (self._config_obj,))
2219
co_input = StringIO(str_or_unicode.encode('utf-8'))
2219
2221
# The config files are always stored utf8-encoded
2220
2222
self._config_obj = ConfigObj(co_input, encoding='utf-8')
2221
2223
except configobj.ConfigObjError, e:
2222
# FIXME: external_url should really accepts an optional relpath
2223
# parameter (bug #750169) :-/ -- vila 2011-04-04
2224
# The following will do in the interim but maybe we don't want to
2225
# expose a path here but rather a config ID and its associated
2226
# object </hand wawe>.
2227
file_path = os.path.join(self.transport.external_url(),
2229
raise errors.ParseConfigError(e.errors, file_path)
2224
self._config_obj = None
2225
raise errors.ParseConfigError(e.errors, self.external_url())
2232
2227
def save(self):
2233
2231
out = StringIO()
2234
2232
self._config_obj.write(out)
2235
2233
self.transport.put_bytes(self.file_name, out.getvalue())
2236
# We don't need the transient content anymore
2237
self._content = None
2235
def external_url(self):
2236
# FIXME: external_url should really accepts an optional relpath
2237
# parameter (bug #750169) :-/ -- vila 2011-04-04
2238
# The following will do in the interim but maybe we don't want to
2239
# expose a path here but rather a config ID and its associated
2240
# object </hand wawe>.
2241
return os.path.join(self.transport.external_url(), self.file_name)
2239
2243
def get_sections(self):
2240
2244
"""Get the configobj section in the file order.
2246
2250
cobj = self._config_obj
2247
2251
if cobj.scalars:
2248
yield ReadOnlySection(None, cobj)
2252
yield self.readonly_section_class(None, cobj)
2249
2253
for section_name in cobj.sections:
2250
yield ReadOnlySection(section_name, cobj[section_name])
2254
yield self.readonly_section_class(section_name, cobj[section_name])
2252
2256
def get_mutable_section(self, section_name=None):
2253
2257
# We need a loaded store
2254
self.load(allow_no_such_file=True)
2260
except errors.NoSuchFile:
2261
# The file doesn't exist, let's pretend it was empty
2262
self._load_from_string('')
2255
2263
if section_name is None:
2256
2264
section = self._config_obj
2258
2266
section = self._config_obj.setdefault(section_name, {})
2259
return MutableSection(section_name, section)
2267
return self.mutable_section_class(section_name, section)
2262
2270
# Note that LockableConfigObjStore inherits from ConfigObjStore because we need
2319
2327
def __init__(self, possible_transports=None):
2320
2328
t = transport.get_transport(config_dir(),
2321
2329
possible_transports=possible_transports)
2322
super(LocationStore, self).__init__(transport, 'locations.conf')
2330
super(LocationStore, self).__init__(t, 'locations.conf')
2325
2333
class BranchStore(ConfigObjStore):