def __init__(self, name, data, file_path): """ :param name: name of the scale, eg "taxes.some_scale" :param data: Data loaded from a YAML file. In case of a reform, the data can also be created dynamically. :param file_path: File the parameter was loaded from. """ self.name: str = name self.file_path: str = file_path helpers._validate_parameter(self, data, data_type=dict, allowed_keys=self._allowed_keys) self.description: str = data.get('description') self.metadata: typing.Dict = {} helpers._set_backward_compatibility_metadata(self, data) self.metadata.update(data.get('metadata', {})) if not isinstance(data.get('brackets', []), list): raise ParameterParsingError( "Property 'brackets' of scale '{}' must be of type array.". format(self.name), self.file_path) brackets = [] for i, bracket_data in enumerate(data.get('brackets', [])): bracket_name = helpers._compose_name(name, item_name=i) bracket = parameters.ParameterScaleBracket(name=bracket_name, data=bracket_data, file_path=file_path) brackets.append(bracket) self.brackets: typing.List[parameters.ParameterScaleBracket] = brackets
def __init__(self, name, data, file_path=None): self.name: str = name self.file_path: str = file_path helpers._validate_parameter(self, data, data_type=dict) self.description: str = None self.metadata: typing.Dict = {} self.documentation: str = None self.values_history = self # Only for backward compatibility # Normal parameter declaration: the values are declared under the 'values' key: parse the description and metadata. if data.get('values'): # 'unit' and 'reference' are only listed here for backward compatibility helpers._validate_parameter(self, data, allowed_keys=config.COMMON_KEYS.union( {'values'})) self.description = data.get('description') helpers._set_backward_compatibility_metadata(self, data) self.metadata.update(data.get('metadata', {})) helpers._validate_parameter(self, data['values'], data_type=dict) values = data['values'] self.documentation = data.get('documentation') else: # Simplified parameter declaration: only values are provided values = data instants = sorted(values.keys(), reverse=True) # sort in reverse chronological order values_list = [] for instant_str in instants: if not periods.INSTANT_PATTERN.match(instant_str): raise ParameterParsingError( "Invalid property '{}' in '{}'. Properties must be valid YYYY-MM-DD instants, such as 2017-01-15." .format(instant_str, self.name), file_path) instant_info = values[instant_str] # Ignore expected values, as they are just metadata if instant_info == "expected" or isinstance( instant_info, dict) and instant_info.get("expected"): continue value_name = helpers._compose_name(name, item_name=instant_str) value_at_instant = ParameterAtInstant(value_name, instant_str, data=instant_info, file_path=self.file_path, metadata=self.metadata) values_list.append(value_at_instant) self.values_list: typing.List[ParameterAtInstant] = values_list
def __getattr__(self, key): param_name = helpers._compose_name(self._name, item_name=key) raise ParameterNotFoundError(param_name, self._instant_str)
def update(self, period=None, start=None, stop=None, value=None): """ Change the value for a given period. :param period: Period where the value is modified. If set, `start` and `stop` should be `None`. :param start: Start of the period. Instance of `openfisca_core.periods.Instant`. If set, `period` should be `None`. :param stop: Stop of the period. Instance of `openfisca_core.periods.Instant`. If set, `period` should be `None`. :param value: New value. If `None`, the parameter is removed from the legislation parameters for the given period. """ if period is not None: if start is not None or stop is not None: raise TypeError( "Wrong input for 'update' method: use either 'update(period, value = value)' or 'update(start = start, stop = stop, value = value)'. You cannot both use 'period' and 'start' or 'stop'." ) if isinstance(period, str): period = periods.period(period) start = period.start stop = period.stop if start is None: raise ValueError("You must provide either a start or a period") start_str = str(start) stop_str = str(stop.offset(1, 'day')) if stop else None old_values = self.values_list new_values = [] n = len(old_values) i = 0 # Future intervals : not affected if stop_str: while (i < n) and (old_values[i].instant_str >= stop_str): new_values.append(old_values[i]) i += 1 # Right-overlapped interval if stop_str: if new_values and (stop_str == new_values[-1].instant_str): pass # such interval is empty else: if i < n: overlapped_value = old_values[i].value value_name = helpers._compose_name(self.name, item_name=stop_str) new_interval = ParameterAtInstant( value_name, stop_str, data={'value': overlapped_value}) new_values.append(new_interval) else: value_name = helpers._compose_name(self.name, item_name=stop_str) new_interval = ParameterAtInstant(value_name, stop_str, data={'value': None}) new_values.append(new_interval) # Insert new interval value_name = helpers._compose_name(self.name, item_name=start_str) new_interval = ParameterAtInstant(value_name, start_str, data={'value': value}) new_values.append(new_interval) # Remove covered intervals while (i < n) and (old_values[i].instant_str >= start_str): i += 1 # Past intervals : not affected while i < n: new_values.append(old_values[i]) i += 1 self.values_list = new_values
def __init__(self, name="", directory_path=None, data=None, file_path=None): """ Instantiate a ParameterNode either from a dict, (using `data`), or from a directory containing YAML files (using `directory_path`). :param string name: Name of the node, eg "taxes.some_tax". :param string directory_path: Directory containing YAML files describing the node. :param dict data: Object representing the parameter node. It usually has been extracted from a YAML file. :param string file_path: YAML file from which the `data` has been extracted from. Instantiate a ParameterNode from a dict: >>> node = ParameterNode('basic_income', data = { 'amount': { 'values': { "2015-01-01": {'value': 550}, "2016-01-01": {'value': 600} } }, 'min_age': { 'values': { "2015-01-01": {'value': 25}, "2016-01-01": {'value': 18} } }, }) Instantiate a ParameterNode from a directory containing YAML parameter files: >>> node = ParameterNode('benefits', directory_path = '/path/to/country_package/parameters/benefits') """ self.name: str = name self.children: typing.Dict[str, typing.Union[ ParameterNode, Parameter, parameters.ParameterScale]] = {} self.description: str = None self.documentation: str = None self.file_path: str = None self.metadata: typing.Dict = {} if directory_path: self.file_path = directory_path for child_name in os.listdir(directory_path): child_path = os.path.join(directory_path, child_name) if os.path.isfile(child_path): child_name, ext = os.path.splitext(child_name) # We ignore non-YAML files if ext not in config.FILE_EXTENSIONS: continue if child_name == 'index': data = helpers._load_yaml_file(child_path) or {} helpers._validate_parameter( self, data, allowed_keys=config.COMMON_KEYS) self.description = data.get('description') self.documentation = data.get('documentation') helpers._set_backward_compatibility_metadata( self, data) self.metadata.update(data.get('metadata', {})) else: child_name_expanded = helpers._compose_name( name, child_name) child = helpers.load_parameter_file( child_path, child_name_expanded) self.add_child(child_name, child) elif os.path.isdir(child_path): child_name = os.path.basename(child_path) child_name_expanded = helpers._compose_name( name, child_name) child = ParameterNode(child_name_expanded, directory_path=child_path) self.add_child(child_name, child) else: self.file_path = file_path helpers._validate_parameter(self, data, data_type=dict, allowed_keys=self._allowed_keys) self.description = data.get('description') self.documentation = data.get('documentation') helpers._set_backward_compatibility_metadata(self, data) self.metadata.update(data.get('metadata', {})) for child_name, child in data.items(): if child_name in config.COMMON_KEYS: continue # do not treat reserved keys as subparameters. child_name = str(child_name) child_name_expanded = helpers._compose_name(name, child_name) child = helpers._parse_child(child_name_expanded, child, file_path) self.add_child(child_name, child)