~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/config.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2010-09-01 08:02:42 UTC
  • mfrom: (5390.3.3 faster-revert-593560)
  • Revision ID: pqm@pqm.ubuntu.com-20100901080242-esg62ody4frwmy66
(spiv) Avoid repeatedly calling self.target.all_file_ids() in
 InterTree.iter_changes. (Andrew Bennetts)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2007, 2008 Canonical Ltd
 
1
# Copyright (C) 2005-2010 Canonical Ltd
2
2
#   Authors: Robert Collins <robert.collins@canonical.com>
3
3
#            and others
4
4
#
65
65
import os
66
66
import sys
67
67
 
 
68
from bzrlib.decorators import needs_write_lock
68
69
from bzrlib.lazy_import import lazy_import
69
70
lazy_import(globals(), """
70
71
import errno
74
75
 
75
76
import bzrlib
76
77
from bzrlib import (
 
78
    atomicfile,
77
79
    debug,
78
80
    errors,
 
81
    lockdir,
79
82
    mail_client,
80
83
    osutils,
81
84
    registry,
82
85
    symbol_versioning,
83
86
    trace,
 
87
    transport,
84
88
    ui,
85
89
    urlutils,
86
90
    win32utils,
153
157
        """Get the users pop up editor."""
154
158
        raise NotImplementedError
155
159
 
 
160
    def get_change_editor(self, old_tree, new_tree):
 
161
        from bzrlib import diff
 
162
        cmd = self._get_change_editor()
 
163
        if cmd is None:
 
164
            return None
 
165
        return diff.DiffFromTool.from_string(cmd, old_tree, new_tree,
 
166
                                             sys.stdout)
 
167
 
 
168
 
156
169
    def get_mail_client(self):
157
170
        """Get a mail client to use"""
158
171
        selected_client = self.get_user_option('mail_client')
181
194
        """Get a generic option as a boolean - no special process, no default.
182
195
 
183
196
        :return None if the option doesn't exist or its value can't be
184
 
            interpreted as a boolean. Returns True or False ortherwise.
 
197
            interpreted as a boolean. Returns True or False otherwise.
185
198
        """
186
199
        s = self._get_user_option(option_name)
187
 
        return ui.bool_from_string(s)
 
200
        if s is None:
 
201
            # The option doesn't exist
 
202
            return None
 
203
        val = ui.bool_from_string(s)
 
204
        if val is None:
 
205
            # The value can't be interpreted as a boolean
 
206
            trace.warning('Value "%s" is not a boolean for "%s"',
 
207
                          s, option_name)
 
208
        return val
 
209
 
 
210
    def get_user_option_as_list(self, option_name):
 
211
        """Get a generic option as a list - no special process, no default.
 
212
 
 
213
        :return None if the option doesn't exist. Returns the value as a list
 
214
            otherwise.
 
215
        """
 
216
        l = self._get_user_option(option_name)
 
217
        if isinstance(l, (str, unicode)):
 
218
            # A single value, most probably the user forgot the final ','
 
219
            l = [l]
 
220
        return l
188
221
 
189
222
    def gpg_signing_command(self):
190
223
        """What program should be used to sign signatures?"""
228
261
 
229
262
        Something similar to 'Martin Pool <mbp@sourcefrog.net>'
230
263
 
231
 
        $BZR_EMAIL can be set to override this (as well as the
232
 
        deprecated $BZREMAIL), then
 
264
        $BZR_EMAIL can be set to override this, then
233
265
        the concrete policy type is checked, and finally
234
266
        $EMAIL is examined.
235
 
        If none is found, a reasonable default is (hopefully)
236
 
        created.
 
267
        If no username can be found, errors.NoWhoami exception is raised.
237
268
 
238
269
        TODO: Check it's reasonably well-formed.
239
270
        """
249
280
        if v:
250
281
            return v.decode(osutils.get_user_encoding())
251
282
 
252
 
        name, email = _auto_user_id()
253
 
        if name:
254
 
            return '%s <%s>' % (name, email)
255
 
        else:
256
 
            return email
 
283
        raise errors.NoWhoami()
 
284
 
 
285
    def ensure_username(self):
 
286
        """Raise errors.NoWhoami if username is not set.
 
287
 
 
288
        This method relies on the username() function raising the error.
 
289
        """
 
290
        self.username()
257
291
 
258
292
    def signature_checking(self):
259
293
        """What is the current policy for signature checking?."""
304
338
                path = 'bzr'
305
339
            return path
306
340
 
 
341
    def suppress_warning(self, warning):
 
342
        """Should the warning be suppressed or emitted.
 
343
 
 
344
        :param warning: The name of the warning being tested.
 
345
 
 
346
        :returns: True if the warning should be suppressed, False otherwise.
 
347
        """
 
348
        warnings = self.get_user_option_as_list('suppress_warnings')
 
349
        if warnings is None or warning not in warnings:
 
350
            return False
 
351
        else:
 
352
            return True
 
353
 
307
354
 
308
355
class IniBasedConfig(Config):
309
356
    """A configuration policy that draws from ini files."""
310
357
 
311
 
    def __init__(self, get_filename):
 
358
    def __init__(self, get_filename=symbol_versioning.DEPRECATED_PARAMETER,
 
359
                 file_name=None):
 
360
        """Base class for configuration files using an ini-like syntax.
 
361
 
 
362
        :param file_name: The configuration file path.
 
363
        """
312
364
        super(IniBasedConfig, self).__init__()
313
 
        self._get_filename = get_filename
 
365
        self.file_name = file_name
 
366
        if symbol_versioning.deprecated_passed(get_filename):
 
367
            symbol_versioning.warn(
 
368
                'IniBasedConfig.__init__(get_filename) was deprecated in 2.3.'
 
369
                ' Use file_name instead.',
 
370
                DeprecationWarning,
 
371
                stacklevel=2)
 
372
            if get_filename is not None:
 
373
                self.file_name = get_filename()
 
374
        else:
 
375
            self.file_name = file_name
 
376
        self._content = None
314
377
        self._parser = None
315
378
 
316
 
    def _get_parser(self, file=None):
 
379
    @classmethod
 
380
    def from_string(cls, str_or_unicode, file_name=None, save=False):
 
381
        """Create a config object from a string.
 
382
 
 
383
        :param str_or_unicode: A string representing the file content. This will
 
384
            be utf-8 encoded.
 
385
 
 
386
        :param file_name: The configuration file path.
 
387
 
 
388
        :param _save: Whether the file should be saved upon creation.
 
389
        """
 
390
        conf = cls(file_name=file_name)
 
391
        conf._create_from_string(str_or_unicode, save)
 
392
        return conf
 
393
 
 
394
    def _create_from_string(self, str_or_unicode, save):
 
395
        self._content = StringIO(str_or_unicode.encode('utf-8'))
 
396
        # Some tests use in-memory configs, some other always need the config
 
397
        # file to exist on disk.
 
398
        if save:
 
399
            self._write_config_file()
 
400
 
 
401
    def _get_parser(self, file=symbol_versioning.DEPRECATED_PARAMETER):
317
402
        if self._parser is not None:
318
403
            return self._parser
319
 
        if file is None:
320
 
            input = self._get_filename()
 
404
        if symbol_versioning.deprecated_passed(file):
 
405
            symbol_versioning.warn(
 
406
                'IniBasedConfig._get_parser(file=xxx) was deprecated in 2.3.'
 
407
                ' Use IniBasedConfig(_content=xxx) instead.',
 
408
                DeprecationWarning,
 
409
                stacklevel=2)
 
410
        if self._content is not None:
 
411
            co_input = self._content
 
412
        elif self.file_name is None:
 
413
            raise AssertionError('We have no content to create the config')
321
414
        else:
322
 
            input = file
 
415
            co_input = self.file_name
323
416
        try:
324
 
            self._parser = ConfigObj(input, encoding='utf-8')
 
417
            self._parser = ConfigObj(co_input, encoding='utf-8')
325
418
        except configobj.ConfigObjError, e:
326
419
            raise errors.ParseConfigError(e.errors, e.config.filename)
 
420
        # Make sure self.reload() will use the right file name
 
421
        self._parser.filename = self.file_name
327
422
        return self._parser
328
423
 
 
424
    def reload(self):
 
425
        """Reload the config file from disk."""
 
426
        if self.file_name is None:
 
427
            raise AssertionError('We need a file name to reload the config')
 
428
        if self._parser is not None:
 
429
            self._parser.reload()
 
430
 
329
431
    def _get_matching_sections(self):
330
432
        """Return an ordered list of (section_name, extra_path) pairs.
331
433
 
346
448
        """Return the policy for the given (section, option_name) pair."""
347
449
        return POLICY_NONE
348
450
 
 
451
    def _get_change_editor(self):
 
452
        return self.get_user_option('change_editor')
 
453
 
349
454
    def _get_signature_checking(self):
350
455
        """See Config._get_signature_checking."""
351
456
        policy = self._get_user_option('check_signatures')
431
536
    def _get_nickname(self):
432
537
        return self.get_user_option('nickname')
433
538
 
434
 
 
435
 
class GlobalConfig(IniBasedConfig):
 
539
    def _write_config_file(self):
 
540
        if self.file_name is None:
 
541
            raise AssertionError('We cannot save, self.file_name is None')
 
542
        conf_dir = os.path.dirname(self.file_name)
 
543
        ensure_config_dir_exists(conf_dir)
 
544
        atomic_file = atomicfile.AtomicFile(self.file_name)
 
545
        self._get_parser().write(atomic_file)
 
546
        atomic_file.commit()
 
547
        atomic_file.close()
 
548
        osutils.copy_ownership_from_path(self.file_name)
 
549
 
 
550
 
 
551
class LockableConfig(IniBasedConfig):
 
552
    """A configuration needing explicit locking for access.
 
553
 
 
554
    If several processes try to write the config file, the accesses need to be
 
555
    serialized.
 
556
 
 
557
    Daughter classes should decorate all methods that update a config with the
 
558
    ``@needs_write_lock`` decorator (they call, directly or indirectly, the
 
559
    ``_write_config_file()`` method. These methods (typically ``set_option()``
 
560
    and variants must reload the config file from disk before calling
 
561
    ``_write_config_file()``), this can be achieved by calling the
 
562
    ``self.reload()`` method. Note that the lock scope should cover both the
 
563
    reading and the writing of the config file which is why the decorator can't
 
564
    be applied to ``_write_config_file()`` only.
 
565
 
 
566
    This should be enough to implement the following logic:
 
567
    - lock for exclusive write access,
 
568
    - reload the config file from disk,
 
569
    - set the new value
 
570
    - unlock
 
571
 
 
572
    This logic guarantees that a writer can update a value without erasing an
 
573
    update made by another writer.
 
574
    """
 
575
 
 
576
    lock_name = 'lock'
 
577
 
 
578
    def __init__(self, file_name):
 
579
        super(LockableConfig, self).__init__(file_name=file_name)
 
580
        self.dir = osutils.dirname(osutils.safe_unicode(self.file_name))
 
581
        self.transport = transport.get_transport(self.dir)
 
582
        self._lock = lockdir.LockDir(self.transport, 'lock')
 
583
 
 
584
    def _create_from_string(self, unicode_bytes, save):
 
585
        super(LockableConfig, self)._create_from_string(unicode_bytes, False)
 
586
        if save:
 
587
            # We need to handle the saving here (as opposed to IniBasedConfig)
 
588
            # to be able to lock
 
589
            self.lock_write()
 
590
            self._write_config_file()
 
591
            self.unlock()
 
592
 
 
593
    def lock_write(self, token=None):
 
594
        """Takes a write lock in the directory containing the config file.
 
595
 
 
596
        If the directory doesn't exist it is created.
 
597
        """
 
598
        ensure_config_dir_exists(self.dir)
 
599
        return self._lock.lock_write(token)
 
600
 
 
601
    def unlock(self):
 
602
        self._lock.unlock()
 
603
 
 
604
    def break_lock(self):
 
605
        self._lock.break_lock()
 
606
 
 
607
    def _write_config_file(self):
 
608
        if self._lock is None or not self._lock.is_held:
 
609
            # NB: if the following exception is raised it probably means a
 
610
            # missing @needs_write_lock decorator on one of the callers.
 
611
            raise errors.ObjectNotLocked(self)
 
612
        super(LockableConfig, self)._write_config_file()
 
613
 
 
614
 
 
615
class GlobalConfig(LockableConfig):
436
616
    """The configuration that should be used for a specific location."""
437
617
 
 
618
    def __init__(self):
 
619
        super(GlobalConfig, self).__init__(file_name=config_filename())
 
620
 
 
621
    @classmethod
 
622
    def from_string(cls, str_or_unicode, save=False):
 
623
        """Create a config object from a string.
 
624
 
 
625
        :param str_or_unicode: A string representing the file content. This
 
626
            will be utf-8 encoded.
 
627
 
 
628
        :param save: Whether the file should be saved upon creation.
 
629
        """
 
630
        conf = cls()
 
631
        conf._create_from_string(str_or_unicode, save)
 
632
        return conf
 
633
 
438
634
    def get_editor(self):
439
635
        return self._get_user_option('editor')
440
636
 
441
 
    def __init__(self):
442
 
        super(GlobalConfig, self).__init__(config_filename)
443
 
 
 
637
    @needs_write_lock
444
638
    def set_user_option(self, option, value):
445
639
        """Save option and its value in the configuration."""
446
640
        self._set_option(option, value, 'DEFAULT')
452
646
        else:
453
647
            return {}
454
648
 
 
649
    @needs_write_lock
455
650
    def set_alias(self, alias_name, alias_command):
456
651
        """Save the alias in the configuration."""
457
652
        self._set_option(alias_name, alias_command, 'ALIASES')
458
653
 
 
654
    @needs_write_lock
459
655
    def unset_alias(self, alias_name):
460
656
        """Unset an existing alias."""
 
657
        self.reload()
461
658
        aliases = self._get_parser().get('ALIASES')
462
659
        if not aliases or alias_name not in aliases:
463
660
            raise errors.NoSuchAlias(alias_name)
465
662
        self._write_config_file()
466
663
 
467
664
    def _set_option(self, option, value, section):
468
 
        # FIXME: RBC 20051029 This should refresh the parser and also take a
469
 
        # file lock on bazaar.conf.
470
 
        conf_dir = os.path.dirname(self._get_filename())
471
 
        ensure_config_dir_exists(conf_dir)
 
665
        self.reload()
472
666
        self._get_parser().setdefault(section, {})[option] = value
473
667
        self._write_config_file()
474
668
 
475
 
    def _write_config_file(self):
476
 
        f = open(self._get_filename(), 'wb')
477
 
        self._get_parser().write(f)
478
 
        f.close()
479
 
 
480
 
 
481
 
class LocationConfig(IniBasedConfig):
 
669
 
 
670
class LocationConfig(LockableConfig):
482
671
    """A configuration object that gives the policy for a location."""
483
672
 
484
673
    def __init__(self, location):
485
 
        name_generator = locations_config_filename
486
 
        if (not os.path.exists(name_generator()) and
487
 
                os.path.exists(branches_config_filename())):
488
 
            if sys.platform == 'win32':
489
 
                trace.warning('Please rename %s to %s'
490
 
                              % (branches_config_filename(),
491
 
                                 locations_config_filename()))
492
 
            else:
493
 
                trace.warning('Please rename ~/.bazaar/branches.conf'
494
 
                              ' to ~/.bazaar/locations.conf')
495
 
            name_generator = branches_config_filename
496
 
        super(LocationConfig, self).__init__(name_generator)
 
674
        super(LocationConfig, self).__init__(
 
675
            file_name=locations_config_filename())
497
676
        # local file locations are looked up by local path, rather than
498
677
        # by file url. This is because the config file is a user
499
678
        # file, and we would rather not expose the user to file urls.
501
680
            location = urlutils.local_path_from_url(location)
502
681
        self.location = location
503
682
 
 
683
    @classmethod
 
684
    def from_string(cls, str_or_unicode, location, save=False):
 
685
        """Create a config object from a string.
 
686
 
 
687
        :param str_or_unicode: A string representing the file content. This will
 
688
            be utf-8 encoded.
 
689
 
 
690
        :param location: The location url to filter the configuration.
 
691
 
 
692
        :param save: Whether the file should be saved upon creation.
 
693
        """
 
694
        conf = cls(location)
 
695
        conf._create_from_string(str_or_unicode, save)
 
696
        return conf
 
697
 
504
698
    def _get_matching_sections(self):
505
699
        """Return an ordered list of section names matching this location."""
506
700
        sections = self._get_parser()
594
788
            if policy_key in self._get_parser()[section]:
595
789
                del self._get_parser()[section][policy_key]
596
790
 
 
791
    @needs_write_lock
597
792
    def set_user_option(self, option, value, store=STORE_LOCATION):
598
793
        """Save option and its value in the configuration."""
599
794
        if store not in [STORE_LOCATION,
601
796
                         STORE_LOCATION_APPENDPATH]:
602
797
            raise ValueError('bad storage policy %r for %r' %
603
798
                (store, option))
604
 
        # FIXME: RBC 20051029 This should refresh the parser and also take a
605
 
        # file lock on locations.conf.
606
 
        conf_dir = os.path.dirname(self._get_filename())
607
 
        ensure_config_dir_exists(conf_dir)
 
799
        self.reload()
608
800
        location = self.location
609
801
        if location.endswith('/'):
610
802
            location = location[:-1]
611
 
        if (not location in self._get_parser() and
612
 
            not location + '/' in self._get_parser()):
613
 
            self._get_parser()[location]={}
614
 
        elif location + '/' in self._get_parser():
 
803
        parser = self._get_parser()
 
804
        if not location in parser and not location + '/' in parser:
 
805
            parser[location] = {}
 
806
        elif location + '/' in parser:
615
807
            location = location + '/'
616
 
        self._get_parser()[location][option]=value
 
808
        parser[location][option]=value
617
809
        # the allowed values of store match the config policies
618
810
        self._set_option_policy(location, option, store)
619
 
        self._get_parser().write(file(self._get_filename(), 'wb'))
 
811
        self._write_config_file()
620
812
 
621
813
 
622
814
class BranchConfig(Config):
623
815
    """A configuration object giving the policy for a branch."""
624
816
 
 
817
    def __init__(self, branch):
 
818
        super(BranchConfig, self).__init__()
 
819
        self._location_config = None
 
820
        self._branch_data_config = None
 
821
        self._global_config = None
 
822
        self.branch = branch
 
823
        self.option_sources = (self._get_location_config,
 
824
                               self._get_branch_data_config,
 
825
                               self._get_global_config)
 
826
 
625
827
    def _get_branch_data_config(self):
626
828
        if self._branch_data_config is None:
627
829
            self._branch_data_config = TreeConfig(self.branch)
679
881
 
680
882
        return self._get_best_value('_get_user_id')
681
883
 
 
884
    def _get_change_editor(self):
 
885
        return self._get_best_value('_get_change_editor')
 
886
 
682
887
    def _get_signature_checking(self):
683
888
        """See Config._get_signature_checking."""
684
889
        return self._get_best_value('_get_signature_checking')
722
927
        """See Config.gpg_signing_command."""
723
928
        return self._get_safe_value('_gpg_signing_command')
724
929
 
725
 
    def __init__(self, branch):
726
 
        super(BranchConfig, self).__init__()
727
 
        self._location_config = None
728
 
        self._branch_data_config = None
729
 
        self._global_config = None
730
 
        self.branch = branch
731
 
        self.option_sources = (self._get_location_config,
732
 
                               self._get_branch_data_config,
733
 
                               self._get_global_config)
734
 
 
735
930
    def _post_commit(self):
736
931
        """See Config.post_commit."""
737
932
        return self._get_safe_value('_post_commit')
770
965
            os.mkdir(parent_dir)
771
966
        trace.mutter('creating config directory: %r', path)
772
967
        os.mkdir(path)
 
968
        osutils.copy_ownership_from_path(path)
773
969
 
774
970
 
775
971
def config_dir():
790
986
                                  ' or HOME set')
791
987
        return osutils.pathjoin(base, 'bazaar', '2.0')
792
988
    else:
793
 
        # cygwin, linux, and darwin all have a $HOME directory
794
989
        if base is None:
795
990
            base = os.path.expanduser("~")
796
991
        return osutils.pathjoin(base, ".bazaar")
801
996
    return osutils.pathjoin(config_dir(), 'bazaar.conf')
802
997
 
803
998
 
804
 
def branches_config_filename():
805
 
    """Return per-user configuration ini file filename."""
806
 
    return osutils.pathjoin(config_dir(), 'branches.conf')
807
 
 
808
 
 
809
999
def locations_config_filename():
810
1000
    """Return per-user configuration ini file filename."""
811
1001
    return osutils.pathjoin(config_dir(), 'locations.conf')
821
1011
    return osutils.pathjoin(config_dir(), 'ignore')
822
1012
 
823
1013
 
824
 
def _auto_user_id():
825
 
    """Calculate automatic user identification.
826
 
 
827
 
    Returns (realname, email).
828
 
 
829
 
    Only used when none is set in the environment or the id file.
830
 
 
831
 
    This previously used the FQDN as the default domain, but that can
832
 
    be very slow on machines where DNS is broken.  So now we simply
833
 
    use the hostname.
 
1014
def crash_dir():
 
1015
    """Return the directory name to store crash files.
 
1016
 
 
1017
    This doesn't implicitly create it.
 
1018
 
 
1019
    On Windows it's in the config directory; elsewhere it's /var/crash
 
1020
    which may be monitored by apport.  It can be overridden by
 
1021
    $APPORT_CRASH_DIR.
834
1022
    """
835
 
    import socket
836
 
 
837
1023
    if sys.platform == 'win32':
838
 
        name = win32utils.get_user_name_unicode()
839
 
        if name is None:
840
 
            raise errors.BzrError("Cannot autodetect user name.\n"
841
 
                                  "Please, set your name with command like:\n"
842
 
                                  'bzr whoami "Your Name <name@domain.com>"')
843
 
        host = win32utils.get_host_name_unicode()
844
 
        if host is None:
845
 
            host = socket.gethostname()
846
 
        return name, (name + '@' + host)
847
 
 
848
 
    try:
849
 
        import pwd
850
 
        uid = os.getuid()
851
 
        try:
852
 
            w = pwd.getpwuid(uid)
853
 
        except KeyError:
854
 
            raise errors.BzrCommandError('Unable to determine your name.  '
855
 
                'Please use "bzr whoami" to set it.')
856
 
 
857
 
        # we try utf-8 first, because on many variants (like Linux),
858
 
        # /etc/passwd "should" be in utf-8, and because it's unlikely to give
859
 
        # false positives.  (many users will have their user encoding set to
860
 
        # latin-1, which cannot raise UnicodeError.)
861
 
        try:
862
 
            gecos = w.pw_gecos.decode('utf-8')
863
 
            encoding = 'utf-8'
864
 
        except UnicodeError:
865
 
            try:
866
 
                encoding = osutils.get_user_encoding()
867
 
                gecos = w.pw_gecos.decode(encoding)
868
 
            except UnicodeError:
869
 
                raise errors.BzrCommandError('Unable to determine your name.  '
870
 
                   'Use "bzr whoami" to set it.')
871
 
        try:
872
 
            username = w.pw_name.decode(encoding)
873
 
        except UnicodeError:
874
 
            raise errors.BzrCommandError('Unable to determine your name.  '
875
 
                'Use "bzr whoami" to set it.')
876
 
 
877
 
        comma = gecos.find(',')
878
 
        if comma == -1:
879
 
            realname = gecos
880
 
        else:
881
 
            realname = gecos[:comma]
882
 
        if not realname:
883
 
            realname = username
884
 
 
885
 
    except ImportError:
886
 
        import getpass
887
 
        try:
888
 
            user_encoding = osutils.get_user_encoding()
889
 
            realname = username = getpass.getuser().decode(user_encoding)
890
 
        except UnicodeDecodeError:
891
 
            raise errors.BzrError("Can't decode username as %s." % \
892
 
                    user_encoding)
893
 
 
894
 
    return realname, (username + '@' + socket.gethostname())
 
1024
        return osutils.pathjoin(config_dir(), 'Crash')
 
1025
    else:
 
1026
        # XXX: hardcoded in apport_python_hook.py; therefore here too -- mbp
 
1027
        # 2010-01-31
 
1028
        return os.environ.get('APPORT_CRASH_DIR', '/var/crash')
 
1029
 
 
1030
 
 
1031
def xdg_cache_dir():
 
1032
    # See http://standards.freedesktop.org/basedir-spec/latest/ar01s03.html
 
1033
    # Possibly this should be different on Windows?
 
1034
    e = os.environ.get('XDG_CACHE_DIR', None)
 
1035
    if e:
 
1036
        return e
 
1037
    else:
 
1038
        return os.path.expanduser('~/.cache')
895
1039
 
896
1040
 
897
1041
def parse_username(username):
985
1129
        """Save the config file, only tests should use it for now."""
986
1130
        conf_dir = os.path.dirname(self._filename)
987
1131
        ensure_config_dir_exists(conf_dir)
988
 
        self._get_config().write(file(self._filename, 'wb'))
 
1132
        f = file(self._filename, 'wb')
 
1133
        try:
 
1134
            self._get_config().write(f)
 
1135
        finally:
 
1136
            f.close()
989
1137
 
990
1138
    def _set_option(self, section_name, option_name, value):
991
1139
        """Set an authentication configuration option"""
1339
1487
 
1340
1488
 
1341
1489
class PlainTextCredentialStore(CredentialStore):
1342
 
    """Plain text credential store for the authentication.conf file."""
 
1490
    __doc__ = """Plain text credential store for the authentication.conf file"""
1343
1491
 
1344
1492
    def decode_password(self, credentials):
1345
1493
        """See CredentialStore.decode_password."""
1434
1582
 
1435
1583
    def _get_config_file(self):
1436
1584
        try:
1437
 
            return self._transport.get(self._filename)
 
1585
            return StringIO(self._transport.get_bytes(self._filename))
1438
1586
        except errors.NoSuchFile:
1439
1587
            return StringIO()
1440
1588
 
1441
1589
    def _get_configobj(self):
1442
 
        return ConfigObj(self._get_config_file(), encoding='utf-8')
 
1590
        f = self._get_config_file()
 
1591
        try:
 
1592
            return ConfigObj(f, encoding='utf-8')
 
1593
        finally:
 
1594
            f.close()
1443
1595
 
1444
1596
    def _set_configobj(self, configobj):
1445
1597
        out_file = StringIO()