from __future__ import annotations
import copy
from openfisca_core.parameters import ParameterNode
from openfisca_core.taxbenefitsystems import TaxBenefitSystem
[docs]
class Reform(TaxBenefitSystem):
    """A modified TaxBenefitSystem.
    All reforms must subclass `Reform` and implement a method `apply()`.
    In this method, the reform can add or replace variables and call `modify_parameters` to modify the parameters of the legislation.
    Example:
        >>> from openfisca_core import reforms
        >>> from openfisca_core.parameters import load_parameter_file
        >>>
        >>> def modify_my_parameters(parameters):
        >>> # Add new parameters
        >>>     new_parameters = load_parameter_file(name='reform_name', file_path='path_to_yaml_file.yaml')
        >>>     parameters.add_child('reform_name', new_parameters)
        >>>
        >>> # Update a value
        >>>     parameters.taxes.some_tax.some_param.update(period=some_period, value=1000.0)
        >>>
        >>>    return parameters
        >>>
        >>> class MyReform(reforms.Reform):
        >>>    def apply(self):
        >>>        self.add_variable(some_variable)
        >>>        self.update_variable(some_other_variable)
        >>>        self.modify_parameters(modifier_function = modify_my_parameters)
    """
    name = None
    def __init__(self, baseline) -> None:
        """:param baseline: Baseline TaxBenefitSystem."""
        super().__init__(baseline.entities)
        self.baseline = baseline
        self.parameters = baseline.parameters
        self._parameters_at_instant_cache = baseline._parameters_at_instant_cache
        self.variables = baseline.variables.copy()
        self.decomposition_file_path = baseline.decomposition_file_path
        self.key = self.__class__.__name__
        if not hasattr(self, "apply"):
            msg = f"Reform {self.key} must define an `apply` function"
            raise Exception(msg)
        self.apply()
    def __getattr__(self, attribute):
        return getattr(self.baseline, attribute)
    @property
    def full_key(self):
        key = self.key
        assert (
            key is not None
        ), f"key was not set for reform {self} (name: {self.name!r})"
        if self.baseline is not None and hasattr(self.baseline, "key"):
            baseline_full_key = self.baseline.full_key
            key = f"{baseline_full_key}.{key}"
        return key
[docs]
    def modify_parameters(self, modifier_function):
        """Make modifications on the parameters of the legislation.
        Call this function in `apply()` if the reform asks for legislation parameter modifications.
        Args:
            modifier_function: A function that takes a :obj:`~parameters.ParameterNode`
            and should return an object of the same type.
        """
        baseline_parameters = self.baseline.parameters
        baseline_parameters_copy = copy.deepcopy(baseline_parameters)
        reform_parameters = modifier_function(baseline_parameters_copy)
        if not isinstance(reform_parameters, ParameterNode):
            raise ValueError(
                f"modifier_function {modifier_function.__name__} in module {modifier_function.__module__} must return a ParameterNode",
            )
        self.parameters = reform_parameters
        self._parameters_at_instant_cache = {}
        return None