diff --git a/openfisca_core/formulas.py b/openfisca_core/formulas.py index 4e01277441..7e2852c5d6 100644 --- a/openfisca_core/formulas.py +++ b/openfisca_core/formulas.py @@ -120,7 +120,7 @@ def __init__(self, holder = None): ) for dated_formula_class in self.dated_formulas_class ] - assert self.dated_formulas + #assert self.dated_formulas @classmethod def at_instant(cls, instant, default = UnboundLocalError): @@ -834,87 +834,87 @@ def new_filled_column( if set_input is not None: formula_class_attributes['set_input'] = set_input - # Turn Variable with a start or a stop into a DatedVariable - if (start_date or stop_date) and formula_class == SimpleFormula and specific_attributes.get('function'): + # Turn function into a decorated function + def is_decorated(function): + return hasattr(function, 'start_instant') or hasattr(function, 'stop_instant') + + if specific_attributes.get('function') and not is_decorated(specific_attributes['function']): specific_attributes['function'] = dated_function(start = start_date, stop = stop_date)(specific_attributes['function']) - formula_class = DatedFormula - if issubclass(formula_class, DatedFormula): + + + + dated_formulas_class = [] + for function_name, function in specific_attributes.copy().iteritems(): + start_instant = getattr(function, 'start_instant', UnboundLocalError) + if start_instant is UnboundLocalError: + # Function is not dated (and may not even be a function). Skip it. + continue + + # Do not accept dated formula with ETERNITY assert column.definition_period != ETERNITY - dated_formulas_class = [] - for function_name, function in specific_attributes.copy().iteritems(): - start_instant = getattr(function, 'start_instant', UnboundLocalError) - if start_instant is UnboundLocalError: - # Function is not dated (and may not even be a function). Skip it. - continue - stop_instant = function.stop_instant - if stop_instant is not None: - assert start_instant <= stop_instant, 'Invalid instant interval for function {}: {} - {}'.format( - function_name, start_instant, stop_instant) - - dated_formula_class_attributes = formula_class_attributes.copy() - dated_formula_class_attributes['function'] = function - dated_formula_class = type(name.encode('utf-8'), (SimpleFormula,), dated_formula_class_attributes) - - del specific_attributes[function_name] - dated_formulas_class.append(dict( - formula_class = dated_formula_class, - start_instant = start_instant, - stop_instant = stop_instant, - )) - # Sort dated formulas by start instant and add missing stop instants. - dated_formulas_class.sort(key = lambda dated_formula_class: dated_formula_class['start_instant']) - if start_date: - dated_formulas_class[0]['start_instant'] = max(dated_formulas_class[0]['start_instant'], instant(start_date)) - if stop_date: - stop_instant = dated_formulas_class[-1]['stop_instant'] - stop_instant = min(stop_instant, instant(stop_date)) if stop_instant else instant(stop_date) - dated_formulas_class[-1]['stop_instant'] = stop_instant - for dated_formula_class, next_dated_formula_class in itertools.izip(dated_formulas_class, - itertools.islice(dated_formulas_class, 1, None)): - if dated_formula_class['stop_instant'] is None: - dated_formula_class['stop_instant'] = next_dated_formula_class['start_instant'].offset(-1, 'day') - else: - assert dated_formula_class['stop_instant'] < next_dated_formula_class['start_instant'], \ - "Dated formulas overlap: {} & {}".format(dated_formula_class, next_dated_formula_class) - - # Add dated formulas defined in (optional) reference column when they are not overridden by new dated - # formulas. - if reference_column is not None and issubclass(reference_column.formula_class, DatedFormula): - for reference_dated_formula_class in reference_column.formula_class.dated_formulas_class: - reference_dated_formula_class = reference_dated_formula_class.copy() - for dated_formula_class in dated_formulas_class: - if reference_dated_formula_class['start_instant'] == dated_formula_class['start_instant'] \ - and reference_dated_formula_class['stop_instant'] == dated_formula_class[ - 'stop_instant']: - break - if reference_dated_formula_class['start_instant'] >= dated_formula_class['start_instant'] \ - and reference_dated_formula_class['start_instant'] < dated_formula_class[ - 'stop_instant']: - reference_dated_formula_class['start_instant'] = dated_formula_class['stop_instant'].offset( - 1, 'day') - if reference_dated_formula_class['stop_instant'] > dated_formula_class['start_instant'] \ - and reference_dated_formula_class['stop_instant'] <= dated_formula_class[ - 'stop_instant']: - reference_dated_formula_class['stop_instant'] = dated_formula_class['start_instant'].offset( - -1, 'day') - if reference_dated_formula_class['start_instant'] > reference_dated_formula_class[ + + + stop_instant = function.stop_instant + if stop_instant is not None: + assert start_instant <= stop_instant, 'Invalid instant interval for function {}: {} - {}'.format( + function_name, start_instant, stop_instant) + + dated_formula_class_attributes = formula_class_attributes.copy() + dated_formula_class_attributes['function'] = function + dated_formula_class = type(name.encode('utf-8'), (SimpleFormula,), dated_formula_class_attributes) + + del specific_attributes[function_name] + dated_formulas_class.append(dict( + formula_class = dated_formula_class, + start_instant = start_instant, + stop_instant = stop_instant, + )) + # Sort dated formulas by start instant and add missing stop instants. + dated_formulas_class.sort(key = lambda dated_formula_class: dated_formula_class['start_instant']) + if start_date: + dated_formulas_class[0]['start_instant'] = max(dated_formulas_class[0]['start_instant'], instant(start_date)) + if stop_date: + stop_instant = dated_formulas_class[-1]['stop_instant'] + stop_instant = min(stop_instant, instant(stop_date)) if stop_instant else instant(stop_date) + dated_formulas_class[-1]['stop_instant'] = stop_instant + for dated_formula_class, next_dated_formula_class in itertools.izip(dated_formulas_class, + itertools.islice(dated_formulas_class, 1, None)): + if dated_formula_class['stop_instant'] is None: + dated_formula_class['stop_instant'] = next_dated_formula_class['start_instant'].offset(-1, 'day') + else: + assert dated_formula_class['stop_instant'] < next_dated_formula_class['start_instant'], \ + "Dated formulas overlap: {} & {}".format(dated_formula_class, next_dated_formula_class) + + # Add dated formulas defined in (optional) reference column when they are not overridden by new dated + # formulas. + if reference_column is not None and issubclass(reference_column.formula_class, DatedFormula): + for reference_dated_formula_class in reference_column.formula_class.dated_formulas_class: + reference_dated_formula_class = reference_dated_formula_class.copy() + for dated_formula_class in dated_formulas_class: + if reference_dated_formula_class['start_instant'] == dated_formula_class['start_instant'] \ + and reference_dated_formula_class['stop_instant'] == dated_formula_class[ 'stop_instant']: - break - else: - dated_formulas_class.append(reference_dated_formula_class) - dated_formulas_class.sort(key = lambda dated_formula_class: dated_formula_class['start_instant']) + break + if reference_dated_formula_class['start_instant'] >= dated_formula_class['start_instant'] \ + and reference_dated_formula_class['start_instant'] < dated_formula_class[ + 'stop_instant']: + reference_dated_formula_class['start_instant'] = dated_formula_class['stop_instant'].offset( + 1, 'day') + if reference_dated_formula_class['stop_instant'] > dated_formula_class['start_instant'] \ + and reference_dated_formula_class['stop_instant'] <= dated_formula_class[ + 'stop_instant']: + reference_dated_formula_class['stop_instant'] = dated_formula_class['start_instant'].offset( + -1, 'day') + if reference_dated_formula_class['start_instant'] > reference_dated_formula_class[ + 'stop_instant']: + break + else: + dated_formulas_class.append(reference_dated_formula_class) + dated_formulas_class.sort(key = lambda dated_formula_class: dated_formula_class['start_instant']) - formula_class_attributes['dated_formulas_class'] = dated_formulas_class - else: - assert issubclass(formula_class, SimpleFormula), formula_class - - function = specific_attributes.pop('function', None) - if column.definition_period == ETERNITY: - assert function is None - if reference_column is not None and function is None: - function = reference_column.formula_class.function - formula_class_attributes['function'] = function + formula_class_attributes['dated_formulas_class'] = dated_formulas_class + # Ensure that all attributes defined in ConversionColumn class are used. assert not specific_attributes, 'Unexpected attributes in definition of variable "{}": {!r}'.format(name, diff --git a/openfisca_core/model_api.py b/openfisca_core/model_api.py index a350f19049..41cc3e8021 100644 --- a/openfisca_core/model_api.py +++ b/openfisca_core/model_api.py @@ -39,7 +39,7 @@ requested_period_last_or_next_value, requested_period_last_value, ) -from .variables import DatedVariable, Variable # noqa analysis:ignore +from .variables import Variable # noqa analysis:ignore from .formula_helpers import apply_thresholds, switch # noqa analysis:ignore from .periods import MONTH, YEAR, ETERNITY # noqa analysis:ignore from .reforms import Reform # noqa analysis:ignore diff --git a/openfisca_core/scripts/measure_performances.py b/openfisca_core/scripts/measure_performances.py index 113cd630e6..7b84dc6858 100644 --- a/openfisca_core/scripts/measure_performances.py +++ b/openfisca_core/scripts/measure_performances.py @@ -19,7 +19,7 @@ from openfisca_core.periods import ETERNITY from openfisca_core.entities import build_entity from openfisca_core.formulas import dated_function -from openfisca_core.variables import DatedVariable, Variable +from openfisca_core.variables import Variable from openfisca_core.taxbenefitsystems import TaxBenefitSystem from openfisca_core.tools import assert_near @@ -139,7 +139,7 @@ def function(self, simulation, period): return rsa + salaire_imposable * 0.7 -class rsa(DatedVariable): +class rsa(Variable): column = FloatCol entity = Individu label = u"RSA" diff --git a/openfisca_core/taxbenefitsystems.py b/openfisca_core/taxbenefitsystems.py index f2b3234e49..6a044d90ab 100644 --- a/openfisca_core/taxbenefitsystems.py +++ b/openfisca_core/taxbenefitsystems.py @@ -156,7 +156,7 @@ def add_variable(self, variable): """ Adds an OpenFisca variable to the tax and benefit system. - :param variable: The variable to add. Must be a subclass of Variable or DatedVariable. + :param variable: The variable to add. Must be a subclass of Variable. :raises: :any:`VariableNameConflict` if a variable with the same name have previously been added to the tax and benefit system. """ @@ -170,7 +170,7 @@ def update_variable(self, variable): If no variable with the given name exists in the tax and benefit system, no error will be raised and the variable will be simply added. - :param variable: Variable to add. Must be a subclass of Variable or DatedVariable. + :param variable: Variable to add. Must be a subclass of Variable. """ return self.load_variable(variable, update = True) diff --git a/openfisca_core/variables.py b/openfisca_core/variables.py index 6c07ba93ab..04f6c755f1 100644 --- a/openfisca_core/variables.py +++ b/openfisca_core/variables.py @@ -4,7 +4,7 @@ import inspect import textwrap -from openfisca_core.formulas import SimpleFormula, DatedFormula, new_filled_column +from openfisca_core.formulas import DatedFormula, new_filled_column class AbstractVariable(object): @@ -62,8 +62,5 @@ def to_column(self, tax_benefit_system): class Variable(AbstractVariable): - formula_class = SimpleFormula - - -class DatedVariable(AbstractVariable): formula_class = DatedFormula + diff --git a/tests/core/test_reforms.py b/tests/core/test_reforms.py index 8aa874cf16..608aabb091 100644 --- a/tests/core/test_reforms.py +++ b/tests/core/test_reforms.py @@ -11,7 +11,7 @@ from openfisca_core.periods import MONTH from openfisca_core.reforms import Reform from openfisca_core.formulas import dated_function -from openfisca_core.variables import Variable, DatedVariable +from openfisca_core.variables import Variable from openfisca_core.periods import Instant from openfisca_core.tools import assert_near from openfisca_country_template.entities import Household @@ -311,7 +311,7 @@ def apply(self): def test_add_dated_variable(): - class new_dated_variable(DatedVariable): + class new_dated_variable(Variable): column = columns.IntCol label = u"Nouvelle variable introduite par la réforme" entity = Household