def _adjust(self, params_or_path, raise_errors=True, extend_adj=True): """ Internal method for performing adjustments. """ params = self.read_params(params_or_path) # Validate user adjustments. parsed_params = {} try: parsed_params = self._validator_schema.load(params) except MarshmallowValidationError as ve: self._parse_errors(ve, params) if not self._errors: if self.label_to_extend is not None and extend_adj: extend_grid = self._stateless_label_grid[self.label_to_extend] to_delete = defaultdict(list) backup = {} for param, vos in parsed_params.items(): for vo in utils.grid_sort(vos, self.label_to_extend, extend_grid): if self.label_to_extend in vo: if (vo[self.label_to_extend] not in self.label_grid[self.label_to_extend]): msg = ( f"{param}[{self.label_to_extend}={vo[self.label_to_extend]}] " f"is not active in the current state: " f"{self.label_to_extend}= " f"{self.label_grid[self.label_to_extend]}." ) warnings.warn(msg) gt = select_gt_ix( self._data[param]["value"], True, { self.label_to_extend: vo[self.label_to_extend] }, extend_grid, tree=self._search_trees.get(param), ) eq = select_eq( gt, True, utils.filter_labels( vo, drop=[self.label_to_extend, "value"]), ) to_delete[param] += [ dict(td, **{"value": None}) for td in eq ] # make copy of value objects since they # are about to be modified backup[param] = copy.deepcopy(self._data[param]["value"]) try: array_first = self.array_first self.array_first = False # delete params that will be overwritten out by extend. self._adjust(to_delete, extend_adj=False, raise_errors=True) # set user adjustments. self._adjust(parsed_params, extend_adj=False, raise_errors=True) self.extend(params=parsed_params.keys(), raise_errors=True) except ValidationError: for param in backup: self._data[param]["value"] = backup[param] finally: self.array_first = array_first else: for param, value in parsed_params.items(): self._update_param(param, value) self._validator_schema.context["spec"] = self if raise_errors and self._errors: raise self.validation_error # Update attrs for params that were adjusted. self._set_state(params=parsed_params.keys()) return parsed_params
def extend( self, label_to_extend=None, label_to_extend_values=None, params=None, raise_errors=True, ): """ Extend parameters along label_to_extend. Raises: InconsistentLabelsException: Value objects do not have consistent labels. """ if label_to_extend is None: label_to_extend = self.label_to_extend spec = self.specification(meta_data=True) if params is not None: spec = { param: self._data[param] for param, data in spec.items() if param in params } extend_grid = (label_to_extend_values or self._stateless_label_grid[label_to_extend]) adjustment = defaultdict(list) for param, data in spec.items(): if not any(label_to_extend in vo for vo in data["value"]): continue extended_vos = set() for vo in sorted( data["value"], key=lambda val: extend_grid.index(val[label_to_extend]), ): hashable_vo = utils.hashable_value_object(vo) if hashable_vo in extended_vos: continue else: extended_vos.add(hashable_vo) gt = select_gt_ix( self._data[param]["value"], False, {label_to_extend: vo[label_to_extend]}, extend_grid, tree=self._search_trees.get(param), ) eq = select_eq( gt, False, utils.filter_labels(vo, drop=["value", label_to_extend]), ) extended_vos.update(map(utils.hashable_value_object, eq)) eq += [vo] defined_vals = {eq_vo[label_to_extend] for eq_vo in eq} missing_vals = sorted( set(extend_grid) - defined_vals, key=lambda val: extend_grid.index(val), ) if not missing_vals: continue extended = defaultdict(list) for val in missing_vals: eg_ix = extend_grid.index(val) if eg_ix == 0: first_defined_value = min( defined_vals, key=lambda val: extend_grid.index(val), ) value_objects = select_eq( eq, True, {label_to_extend: first_defined_value}) elif extend_grid[eg_ix - 1] in extended: value_objects = extended.pop(extend_grid[eg_ix - 1]) else: prev_defined_value = extend_grid[eg_ix - 1] value_objects = select_eq( eq, True, {label_to_extend: prev_defined_value}) # In practice, value_objects has length one. # Theoretically, there could be multiple if the inital value # object had less labels than later value objects and thus # matched multiple value objects. for value_object in value_objects: ext = dict(value_object, **{label_to_extend: val}) ext = self.extend_func( param, ext, value_object, extend_grid, label_to_extend, ) extended_vos.add( utils.hashable_value_object(value_object)) extended[val].append(ext) adjustment[param].append(ext) # Ensure that the adjust method of paramtools.Parameter is used # in case the child class also implements adjust. self._adjust(adjustment, extend_adj=False, raise_errors=raise_errors)
def adjust(self, params_or_path, raise_errors=True, extend_adj=True): """ Deserialize and validate parameter adjustments. `params_or_path` can be a file path or a `dict` that has not been fully deserialized. The adjusted values replace the current values stored in the corresponding parameter attributes. Returns: parsed, validated parameters. Raises: marshmallow.exceptions.ValidationError if data is not valid. ParameterUpdateException if label values do not match at least one existing value item's corresponding label values. """ params = self.read_params(params_or_path) # Validate user adjustments. parsed_params = {} try: parsed_params = self._validator_schema.load(params) except MarshmallowValidationError as ve: self._parse_errors(ve, params) if not self._errors: if self.label_to_extend is not None and extend_adj: extend_grid = self._stateless_label_grid[self.label_to_extend] for param, vos in parsed_params.items(): to_delete = [] for vo in utils.grid_sort(vos, self.label_to_extend, extend_grid): if self.label_to_extend in vo: if (vo[self.label_to_extend] not in self.label_grid[self.label_to_extend]): msg = ( f"{param}[{self.label_to_extend}={vo[self.label_to_extend]}] " f"is not active in the current state: " f"{self.label_to_extend}= " f"{self.label_grid[self.label_to_extend]}." ) warnings.warn(msg) gt = select_gt_ix( self._data[param]["value"], True, { self.label_to_extend: vo[self.label_to_extend] }, extend_grid, ) eq = select_eq( gt, True, utils.filter_labels( vo, drop=[self.label_to_extend, "value"]), ) to_delete += eq to_delete = [ dict(td, **{"value": None}) for td in to_delete ] # make copy of value objects since they # are about to be modified backup = copy.deepcopy(self._data[param]["value"]) try: array_first = self.array_first self.array_first = False # delete params that will be overwritten out by extend. self.adjust( {param: to_delete}, extend_adj=False, raise_errors=True, ) # set user adjustments. self.adjust({param: vos}, extend_adj=False, raise_errors=True) self.array_first = array_first # extend user adjustments. self.extend(params=[param], raise_errors=True) except ValidationError: self._data[param]["value"] = backup else: for param, value in parsed_params.items(): self._update_param(param, value) self._validator_schema.context["spec"] = self if raise_errors and self._errors: raise self.validation_error # Update attrs for params that were adjusted. self._set_state(params=parsed_params.keys()) return parsed_params