def test_update(self): # Bags can be updated, similar to dicts. bag = Bag(a=1, b=2, c=3) bag.update(b=7, d=9) self.assertEqual(bag.a, 1) self.assertEqual(bag.b, 7) self.assertEqual(bag.c, 3) self.assertEqual(bag.d, 9)
def test_update_converter_overrides(self): # Converters in the update method permanently override ctor converters. converters = dict(a=int, b=int) bag = Bag(converters=converters, a='1', b='2') self.assertEqual(bag.a, 1) self.assertEqual(bag.b, 2) new_converters = dict(a=str) bag.update(converters=new_converters, a='3', b='4') self.assertEqual(bag.a, '3') self.assertEqual(bag.b, 4) bag.update(a='5', b='6') self.assertEqual(bag.a, '5') self.assertEqual(bag.b, 6)
def test_update_converters(self): # The update method also accepts converters. bag = Bag(a=1, b=2, c=3) bag.update(converters=dict(d=int), d='4', e='5') self.assertEqual(bag.d, 4) self.assertEqual(bag.e, '5')
class Configuration: def __init__(self, directory=None): self._set_defaults() # Because the configuration object is a global singleton, it makes for # a convenient place to stash information used by widely separate # components. For example, this is a placeholder for rendezvous # between the downloader and the D-Bus service. When running under # D-Bus and we get a `paused` signal from the download manager, we need # this to plumb through an UpdatePaused signal to our clients. It # rather sucks that we need a global for this, but I can't get the # plumbing to work otherwise. This seems like the least horrible place # to stash this global. self.dbus_service = None # These are used to plumb command line arguments from the main() to # other parts of the system. self.skip_gpg_verification = False self.override_gsm = False # Cache. self._device = None self._build_number = None self.build_number_override = False self._channel = None # This is used only to override the phased percentage via command line # and the property setter. self._phase_override = None self._tempdir = None self.config_d = None self.ini_files = [] self.http_base = None self.https_base = None if directory is not None: self.load(directory) self._calculate_http_bases() self._resources = ExitStack() atexit.register(self._resources.close) def _set_defaults(self): self.service = Bag( base='system-image.ubports.com', http_port=80, https_port=443, channel='daily', build_number=0, ) self.system = Bag( timeout=as_timedelta('1h'), tempdir='/tmp', logfile='/var/log/system-image/client.log', loglevel=as_loglevel('info'), settings_db='/var/lib/system-image/settings.db', ) self.gpg = Bag( archive_master='/usr/share/system-image/archive-master.tar.xz', image_master='/var/lib/system-image/keyrings/image-master.tar.xz', image_signing='/var/lib/system-image/keyrings/image-signing.tar.xz', device_signing= '/var/lib/system-image/keyrings/device-signing.tar.xz', ) self.updater = Bag( cache_partition='/android/cache/recovery', data_partition='/var/lib/system-image', ) self.hooks = Bag( device=as_object('systemimage.device.SystemProperty'), scorer=as_object('systemimage.scores.WeightedScorer'), apply=as_object('systemimage.apply.Reboot'), ) self.dbus = Bag(lifetime=as_timedelta('10m'), ) def _load_file(self, path): parser = SafeConfigParser() str_path = str(path) parser.read(str_path) self.ini_files.append(path) self.service.update(converters=dict( http_port=as_port, https_port=as_port, build_number=int, device=as_stripped, ), **parser['service']) self.system.update(converters=dict(timeout=as_timedelta, loglevel=as_loglevel, settings_db=expand_path, tempdir=expand_path), **parser['system']) self.gpg.update(**parser['gpg']) self.updater.update(**parser['updater']) self.hooks.update(converters=dict(device=as_object, scorer=as_object, apply=as_object), **parser['hooks']) self.dbus.update(converters=dict(lifetime=as_timedelta), **parser['dbus']) def load(self, directory): """Load up the configuration from a config.d directory.""" # Look for all the files in the given directory with .ini or .cfg # suffixes. The files must start with a number, and the files are # loaded in numeric order. if self.config_d is not None: raise RuntimeError('Configuration already loaded; use .reload()') self.config_d = directory if not Path(directory).is_dir(): raise TypeError( '.load() requires a directory: {}'.format(directory)) candidates = [] for child in Path(directory).glob('*.ini'): order, _, base = child.stem.partition('_') # XXX 2014-10-03: The logging system isn't initialized when we get # here, so we can't log that these files are being ignored. if len(_) == 0: continue try: serial = int(order) except ValueError: continue candidates.append((serial, child)) for serial, path in sorted(candidates): self._load_file(path) self._calculate_http_bases() def reload(self): """Reload the configuration directory.""" # Reset some cached attributes. directory = self.config_d self.ini_files = [] self.config_d = None self._build_number = None # Now load the defaults, then reload the previous config.d directory. self._set_defaults() self.load(directory) def _calculate_http_bases(self): if (self.service.http_port is NO_PORT and self.service.https_port is NO_PORT): raise ValueError('Cannot disable both http and https ports') # Construct the HTTP and HTTPS base urls, which most applications will # actually use. We do this in two steps, in order to support disabling # one or the other (but not both) protocols. if self.service.http_port == 80: http_base = 'http://{}'.format(self.service.base) elif self.service.http_port is NO_PORT: http_base = None else: http_base = 'http://{}:{}'.format(self.service.base, self.service.http_port) # HTTPS. if self.service.https_port == 443: https_base = 'https://{}'.format(self.service.base) elif self.service.https_port is NO_PORT: https_base = None else: https_base = 'https://{}:{}'.format(self.service.base, self.service.https_port) # Sanity check and final settings. if http_base is None: assert https_base is not None http_base = https_base if https_base is None: assert http_base is not None https_base = http_base self.http_base = http_base self.https_base = https_base @property def build_number(self): if self._build_number is None: self._build_number = self.service.build_number return self._build_number @build_number.setter def build_number(self, value): if not isinstance(value, int): raise ValueError('integer is required, got: {}'.format( type(value).__name__)) self._build_number = value self.build_number_override = True @build_number.deleter def build_number(self): self._build_number = None @property def device(self): if self._device is None: # Start by looking for a [service]device setting. Use this if it # exists, otherwise fall back to calling the hook. self._device = getattr(self.service, 'device', None) if not self._device: self._device = self.hooks.device().get_device() return self._device @device.setter def device(self, value): self._device = value @property def channel(self): if self._channel is None: self._channel = self.service.channel return self._channel @channel.setter def channel(self, value): self._channel = value @property def phase_override(self): return self._phase_override @phase_override.setter def phase_override(self, value): self._phase_override = max(0, min(100, int(value))) @phase_override.deleter def phase_override(self): self._phase_override = None @property def tempdir(self): if self._tempdir is None: makedirs(self.system.tempdir) self._tempdir = self._resources.enter_context( temporary_directory(prefix='system-image-', dir=self.system.tempdir)) return self._tempdir @property def user_agent(self): return USER_AGENT.format(self)