def parse_configs(framework): """ Parse all available config definition files as for all availbale apps and for project itself at last. Also we need to read additional types and additional default values before parsing start. """ additional_types = framework.get_additional_types() all_settings = SettingsContainer() default_values = {} fail_silently = framework.fail_silently # Use ``OrderedDict`` instance for reading sections on config file instead # of default ``dict`` that can shuffle the sections. config = ConfigParser(dict_type=OrderedDict) # First we need to read default values file default_values_file = framework.find_default_values_file() if default_values_file: config.no_sections_mode = True try: config.read(default_values_file) except ConfigParserError: logger.exception('Cannot read default values from %r', default_values_file) else: default_values = config.defaults() finally: config.no_sections_mode = False # Read available settings files from framework pathes = framework.find_settings_files() for app_name, path in pathes.items(): if app_name == '__project__': continue settings = \ parse_config(path, additional_types, default_values, app_name) all_settings.add(app_name, settings) # And finally read project configuration definition file if any path = pathes.get('__project__') settings = parse_config(path, additional_types, default_values, all_settings=all_settings) all_settings.add('__project__', settings) all_settings.path = settings.path return all_settings
def get_additional_types(self): """ Return tuple of additional types that would be supported when parsing config definition files. """ additional_types = self.additional_types or () types = [] for item in additional_types: try: additional_type = load_from_path(item) except (AttributeError, TypeError): logger.exception("Cannot load %r additional setting type " "from configuration", item) continue types.append(additional_type) return tuple(types)
def _parse_validators(self, value): """ Parse validators string and try to convert it to list with actual validator functions. """ if not value: return [] items = map(lambda item: item.strip(), value.split(',')) validators = [] for item in items: try: validator = load_from_path(item) except (AttributeError, ImportError): logger.exception('Cannot load %r validator for %s setting.', item, self.name) continue validators.append(validator) return validators
def _parse_choices(self, value): """ Convert string value to valid choices tuple. **Supported formats:** * a, b, c * (a, A), (b, B), (c, C) * a { b, c }, d { e, f } * A { (b, B), (c, C) }, D { (e, E), (f, F) } * path.to.CHOICES * path.to.Model.CHOICES """ # Start parsing with internal choices if not ',' in value and '.' in value: # Choices tuple should be last part of value path, attr = value.rsplit('.', 1) # Load choices from module try: module = importlib.import_module(path) except ImportError: # Or from module class try: module = load_from_path(path) except (AttributeError, ImportError): logger.exception('Cannot load choices from %r path', value) return () # Try to get choices attr in module try: choices = getattr(module, attr) except AttributeError: logger.exception('Cannot load choices from %r path', value) return () elif not '{' in value and not '}' in value: # Parse choice with labels label_re = re.compile(r'\(([^,]+),\s+([^\)]+)\)', re.M) found = label_re.findall(value) if found: choices = found # If nothing found by regex, just split value by comma and # duplicate resulted items else: choices = map(lambda item: (item.strip(), item.strip()), value.split(',')) else: # Parse groups groups_re = re.compile(r'([^{]+){([^}]+)},?', re.M) found = groups_re.findall(value) if found: choices = [] for group, data in found: group = group.strip() choices.append((group, self._parse_choices(data.strip()))) else: logger.error('Cannot parse choices from %r', value) return () return tuple(choices)
def parse_config(path, additional_types=None, default_values=None, app_name=None, all_settings=None): """ Parse Configuration Definition File. In most cases this file needs to be placed in same folder where project settings module exist and named as ``settings.cfg``. But you can customize things with using ``SETMAN_SETTINGS_FILE`` option. Provide there path where settings file actually placed. Also current function can called with ``path`` string. """ config = ConfigParser(dict_type=OrderedDict) empty_settings = SettingsContainer(path, app_name) try: config.read(path) except ConfigParserError: logger.exception('Cannot parse configuration definition file from ' \ '%r', path) return empty_settings settings = copy.deepcopy(empty_settings) for setting in config.sections(): if '.' in setting: full_setting = setting app_name, setting = setting.split('.', 1) try: app_settings = getattr(all_settings, app_name) except AttributeError: logger.exception('Cannot find settings for %r app', app_name) continue try: app_setting = getattr(app_settings, setting) except AttributeError: logger.exception('Cannot find %r app setting %r', app_name, setting) continue data = dict(config.items(full_setting)) if default_values and full_setting in default_values: data.update({'default': default_values[setting]}) try: app_setting = update_app_setting(app_setting, data) except ValueError: logger.exception('Cannot redefine ``type`` attr for %r ' \ 'setting', full_setting) continue else: data = dict(config.items(setting)) data.update({'app_name': app_name, 'name': setting}) if default_values and setting in default_values: data.update({'default': default_values[setting]}) try: setting = data_to_setting(data, additional_types) except SettingTypeDoesNotExist: logger.exception('Cannot find proper setting class for %r ' \ 'type', data.get('type')) return empty_settings settings.add(setting.name, setting) return settings