From 48c43ba35bcde0a65838a792fc11ba01d5e2bd67 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Tue, 24 Sep 2024 01:51:19 +0200 Subject: [PATCH 1/6] feat(tests): add assert_near parser --- STYLEGUIDE.md | 5 +- openfisca_core/holders/tests/test_helpers.py | 7 +- openfisca_core/indexed_enums/__init__.py | 9 +- openfisca_core/indexed_enums/enum.py | 1 + openfisca_core/indexed_enums/enum_array.py | 6 +- openfisca_core/indexed_enums/types.py | 3 + openfisca_core/periods/instant_.py | 2 +- .../scripts/measure_performances.py | 2 +- openfisca_core/tools/__init__.py | 67 +---- openfisca_core/tools/test_runner.py | 2 +- openfisca_core/types.py | 33 ++- openfisca_core/variables/__init__.py | 16 +- openfisca_core/variables/config.py | 19 +- openfisca_core/variables/types.py | 17 ++ openfisca_tasks/lint.mk | 12 +- openfisca_tasks/test_code.mk | 3 +- openfisca_test/__init__.py | 5 + openfisca_test/_assertions.py | 0 openfisca_test/_parsers.py | 245 ++++++++++++++++++ openfisca_test/tests/__init__.py | 0 .../tests}/test_assert_near.py | 0 openfisca_test/tests/test_parse.py | 58 +++++ openfisca_test/types.py | 21 ++ setup.cfg | 18 +- setup.py | 2 +- stubs/numexpr/__init__.pyi | 13 + stubs/pendulum/__init__.pyi | 3 + .../test_date_indexing.py | 2 +- .../test_fancy_indexing.py | 2 +- .../test_linear_average_rate_tax_scale.py | 13 +- .../test_marginal_amount_tax_scale.py | 5 +- .../test_marginal_rate_tax_scale.py | 29 ++- .../test_single_amount_tax_scale.py | 5 +- .../tax_scales/test_tax_scales_commons.py | 7 +- tests/core/test_calculate_output.py | 5 +- tests/core/test_countries.py | 13 +- tests/core/test_cycles.py | 13 +- tests/core/test_entities.py | 126 ++++----- tests/core/test_holders.py | 5 +- tests/core/test_reforms.py | 2 +- tests/core/test_simulation_builder.py | 18 +- tests/core/variables/test_variables.py | 2 +- 42 files changed, 596 insertions(+), 220 deletions(-) create mode 100644 openfisca_core/indexed_enums/types.py create mode 100644 openfisca_core/variables/types.py create mode 100644 openfisca_test/__init__.py create mode 100644 openfisca_test/_assertions.py create mode 100644 openfisca_test/_parsers.py create mode 100644 openfisca_test/tests/__init__.py rename {tests/core/tools => openfisca_test/tests}/test_assert_near.py (100%) create mode 100644 openfisca_test/tests/test_parse.py create mode 100644 openfisca_test/types.py create mode 100644 stubs/numexpr/__init__.pyi create mode 100644 stubs/pendulum/__init__.pyi diff --git a/STYLEGUIDE.md b/STYLEGUIDE.md index 97b0461aa3..eb46d446bf 100644 --- a/STYLEGUIDE.md +++ b/STYLEGUIDE.md @@ -72,9 +72,8 @@ And avoid: # No from openfisca_country_template.entities import Person -from openfisca_core import variables -from openfisca_core.tools import assert_near -from openfisca_core import axes +from openfisca_core import axes, variables +from openfisca_test import assert_near from numpy import ndarray from copy import deepcopy diff --git a/openfisca_core/holders/tests/test_helpers.py b/openfisca_core/holders/tests/test_helpers.py index 948f25288f..c76f01f88e 100644 --- a/openfisca_core/holders/tests/test_helpers.py +++ b/openfisca_core/holders/tests/test_helpers.py @@ -1,6 +1,7 @@ import pytest -from openfisca_core import holders, tools +import openfisca_test as test +from openfisca_core import holders from openfisca_core.entities import Entity from openfisca_core.holders import Holder from openfisca_core.periods import DateUnit, Instant, Period @@ -81,7 +82,7 @@ def test_set_input_dispatch_by_period( holders.set_input_dispatch_by_period(holder, dispatch_period, values) total = sum(map(holder.get_array, holder.get_known_periods())) - tools.assert_near(total, expected, absolute_error_margin=0.001) + test.assert_near(total, expected, absolute_error_margin=0.001) @pytest.mark.parametrize( @@ -131,4 +132,4 @@ def test_set_input_divide_by_period( holders.set_input_divide_by_period(holder, divide_period, values) last = holder.get_array(holder.get_known_periods()[-1]) - tools.assert_near(last, expected, absolute_error_margin=0.001) + test.assert_near(last, expected, absolute_error_margin=0.001) diff --git a/openfisca_core/indexed_enums/__init__.py b/openfisca_core/indexed_enums/__init__.py index 874a7e1f9b..9d63bf39f4 100644 --- a/openfisca_core/indexed_enums/__init__.py +++ b/openfisca_core/indexed_enums/__init__.py @@ -21,6 +21,9 @@ # # See: https://www.python.org/dev/peps/pep-0008/#imports -from .config import ENUM_ARRAY_DTYPE # noqa: F401 -from .enum import Enum # noqa: F401 -from .enum_array import EnumArray # noqa: F401 +from . import types +from .config import ENUM_ARRAY_DTYPE +from .enum import Enum +from .enum_array import EnumArray + +__all__ = ["ENUM_ARRAY_DTYPE", "Enum", "EnumArray", "types"] diff --git a/openfisca_core/indexed_enums/enum.py b/openfisca_core/indexed_enums/enum.py index 25b02cee74..4f2c887dac 100644 --- a/openfisca_core/indexed_enums/enum.py +++ b/openfisca_core/indexed_enums/enum.py @@ -4,6 +4,7 @@ import numpy +from . import types as t from .config import ENUM_ARRAY_DTYPE from .enum_array import EnumArray diff --git a/openfisca_core/indexed_enums/enum_array.py b/openfisca_core/indexed_enums/enum_array.py index 86b55f9f48..4ce38809e0 100644 --- a/openfisca_core/indexed_enums/enum_array.py +++ b/openfisca_core/indexed_enums/enum_array.py @@ -5,6 +5,8 @@ import numpy +from . import types as t + if typing.TYPE_CHECKING: from openfisca_core.indexed_enums import Enum @@ -20,8 +22,8 @@ class EnumArray(numpy.ndarray): # https://docs.scipy.org/doc/numpy-1.13.0/user/basics.subclassing.html#slightly-more-realistic-example-attribute-added-to-existing-array. def __new__( cls, - input_array: numpy.int_, - possible_values: type[Enum] | None = None, + input_array: t.Array[t.ArrayEnum], + possible_values: Optional[Type[Enum]] = None, ) -> EnumArray: obj = numpy.asarray(input_array).view(cls) obj.possible_values = possible_values diff --git a/openfisca_core/indexed_enums/types.py b/openfisca_core/indexed_enums/types.py new file mode 100644 index 0000000000..8c725bf8f0 --- /dev/null +++ b/openfisca_core/indexed_enums/types.py @@ -0,0 +1,3 @@ +from openfisca_core.types import Array, ArrayBytes, ArrayEnum + +__all__ = ["Array", "ArrayBytes", "ArrayEnum"] diff --git a/openfisca_core/periods/instant_.py b/openfisca_core/periods/instant_.py index 5042209492..757c543222 100644 --- a/openfisca_core/periods/instant_.py +++ b/openfisca_core/periods/instant_.py @@ -90,7 +90,7 @@ def __str__(self) -> str: return instant_str @property - def date(self): + def date(self) -> pendulum.Date: instant_date = config.date_by_instant_cache.get(self) if instant_date is None: diff --git a/openfisca_core/scripts/measure_performances.py b/openfisca_core/scripts/measure_performances.py index 48b99c93f8..45ed014e2c 100644 --- a/openfisca_core/scripts/measure_performances.py +++ b/openfisca_core/scripts/measure_performances.py @@ -16,8 +16,8 @@ from openfisca_core.entities import build_entity from openfisca_core.periods import DateUnit from openfisca_core.taxbenefitsystems import TaxBenefitSystem -from openfisca_core.tools import assert_near from openfisca_core.variables import Variable +from openfisca_test import assert_near args = None diff --git a/openfisca_core/tools/__init__.py b/openfisca_core/tools/__init__.py index 1416ed1529..5ed40c8d78 100644 --- a/openfisca_core/tools/__init__.py +++ b/openfisca_core/tools/__init__.py @@ -1,67 +1,10 @@ +from __future__ import annotations + import os import numexpr -from openfisca_core.indexed_enums import EnumArray - - -def assert_near( - value, - target_value, - absolute_error_margin=None, - message="", - relative_error_margin=None, -): - """:param value: Value returned by the test - :param target_value: Value that the test should return to pass - :param absolute_error_margin: Absolute error margin authorized - :param message: Error message to be displayed if the test fails - :param relative_error_margin: Relative error margin authorized - - Limit : This function cannot be used to assert near periods. - - """ - import numpy - - if absolute_error_margin is None and relative_error_margin is None: - absolute_error_margin = 0 - if not isinstance(value, numpy.ndarray): - value = numpy.array(value) - if isinstance(value, EnumArray): - return assert_enum_equals(value, target_value, message) - if numpy.issubdtype(value.dtype, numpy.datetime64): - target_value = numpy.array(target_value, dtype=value.dtype) - assert_datetime_equals(value, target_value, message) - if isinstance(target_value, str): - target_value = eval_expression(target_value) - - target_value = numpy.array(target_value).astype(numpy.float32) - - value = numpy.array(value).astype(numpy.float32) - diff = abs(target_value - value) - if absolute_error_margin is not None: - assert ( - diff <= absolute_error_margin - ).all(), f"{message}{value} differs from {target_value} with an absolute margin {diff} > {absolute_error_margin}" - if relative_error_margin is not None: - assert ( - diff <= abs(relative_error_margin * target_value) - ).all(), f"{message}{value} differs from {target_value} with a relative margin {diff} > {abs(relative_error_margin * target_value)}" - return None - return None - - -def assert_datetime_equals(value, target_value, message="") -> None: - assert ( - value == target_value - ).all(), f"{message}{value} differs from {target_value}." - - -def assert_enum_equals(value, target_value, message="") -> None: - value = value.decode_to_str() - assert ( - value == target_value - ).all(), f"{message}{value} differs from {target_value}." +from openfisca_core import types as t def indent(text): @@ -89,7 +32,9 @@ def get_trace_tool_link(scenario, variables, api_url, trace_tool_url): ) -def eval_expression(expression): +def eval_expression( + expression: str, +) -> str | t.Array[t.ArrayBool | t.ArrayInt | t.ArrayFloat]: try: return numexpr.evaluate(expression) except (KeyError, TypeError): diff --git a/openfisca_core/tools/test_runner.py b/openfisca_core/tools/test_runner.py index fcb5572b79..ef19508314 100644 --- a/openfisca_core/tools/test_runner.py +++ b/openfisca_core/tools/test_runner.py @@ -18,8 +18,8 @@ from openfisca_core.errors import SituationParsingError, VariableNotFound from openfisca_core.simulation_builder import SimulationBuilder -from openfisca_core.tools import assert_near from openfisca_core.warnings import LibYAMLWarning +from openfisca_test import assert_near class Options(TypedDict, total=False): diff --git a/openfisca_core/types.py b/openfisca_core/types.py index 16a1f0e90e..3717dfc8b3 100644 --- a/openfisca_core/types.py +++ b/openfisca_core/types.py @@ -10,11 +10,42 @@ import numpy +# Type aliases + +#: Type for arrays of any type. +ArrayAny: TypeAlias = numpy.generic + +#: Type for arrays of booleans. +ArrayBool: TypeAlias = numpy.bool_ + +#: Type for arrays of bytes. +ArrayBytes: TypeAlias = numpy.bytes_ + +#: Type for arrays of dates. +ArrayDate: TypeAlias = numpy.datetime64 + +#: Type for arrays of enums. +ArrayEnum: TypeAlias = numpy.int16 + +#: Type for arrays of floats. +ArrayFloat: TypeAlias = numpy.float32 + +#: Type for arrays of integers. +ArrayInt: TypeAlias = numpy.int32 + +#: Type for arrays of Python objects. +ArrayObject: TypeAlias = numpy.object_ + +#: Type for arrays of strings. +ArrayStr: TypeAlias = numpy.str_ + +#: Generic numpy type an array can have. N = TypeVar("N", bound=numpy.generic, covariant=True) -#: Type representing an numpy array. +#: Type representing a numpy array. Array: TypeAlias = NDArray[N] +#: Generic type a sequence can have. L = TypeVar("L") #: Type representing an array-like object. diff --git a/openfisca_core/variables/__init__.py b/openfisca_core/variables/__init__.py index 1ab191c5ce..e4ec339ca8 100644 --- a/openfisca_core/variables/__init__.py +++ b/openfisca_core/variables/__init__.py @@ -21,6 +21,16 @@ # # See: https://www.python.org/dev/peps/pep-0008/#imports -from .config import FORMULA_NAME_PREFIX, VALUE_TYPES # noqa: F401 -from .helpers import get_annualized_variable, get_neutralized_variable # noqa: F401 -from .variable import Variable # noqa: F401 +from . import types +from .config import FORMULA_NAME_PREFIX, VALUE_TYPES +from .helpers import get_annualized_variable, get_neutralized_variable +from .variable import Variable + +__all__ = [ + "FORMULA_NAME_PREFIX", + "VALUE_TYPES", + "Variable", + "get_annualized_variable", + "get_neutralized_variable", + "types", +] diff --git a/openfisca_core/variables/config.py b/openfisca_core/variables/config.py index 54270145bf..41862af59f 100644 --- a/openfisca_core/variables/config.py +++ b/openfisca_core/variables/config.py @@ -1,47 +1,46 @@ import datetime -import numpy +from openfisca_core import indexed_enums as enum -from openfisca_core import indexed_enums -from openfisca_core.indexed_enums import Enum +from . import types as t VALUE_TYPES = { bool: { - "dtype": numpy.bool_, + "dtype": t.ArrayBool, "default": False, "json_type": "boolean", "formatted_value_type": "Boolean", "is_period_size_independent": True, }, int: { - "dtype": numpy.int32, + "dtype": t.ArrayInt, "default": 0, "json_type": "integer", "formatted_value_type": "Int", "is_period_size_independent": False, }, float: { - "dtype": numpy.float32, + "dtype": t.ArrayFloat, "default": 0, "json_type": "number", "formatted_value_type": "Float", "is_period_size_independent": False, }, str: { - "dtype": object, + "dtype": t.ArrayBytes, "default": "", "json_type": "string", "formatted_value_type": "String", "is_period_size_independent": True, }, - Enum: { - "dtype": indexed_enums.ENUM_ARRAY_DTYPE, + enum.Enum: { + "dtype": t.ArrayEnum, "json_type": "string", "formatted_value_type": "String", "is_period_size_independent": True, }, datetime.date: { - "dtype": "datetime64[D]", + "dtype": t.ArrayDate, "default": datetime.date.fromtimestamp(0), # 0 == 1970-01-01 "json_type": "string", "formatted_value_type": "Date", diff --git a/openfisca_core/variables/types.py b/openfisca_core/variables/types.py new file mode 100644 index 0000000000..62f4c13913 --- /dev/null +++ b/openfisca_core/variables/types.py @@ -0,0 +1,17 @@ +from openfisca_core.types import ( + ArrayBool, + ArrayBytes, + ArrayDate, + ArrayEnum, + ArrayFloat, + ArrayInt, +) + +__any__ = [ + ArrayBool, + ArrayBytes, + ArrayDate, + ArrayEnum, + ArrayFloat, + ArrayInt, +] diff --git a/openfisca_tasks/lint.mk b/openfisca_tasks/lint.mk index 445abba10b..3b7e64dce0 100644 --- a/openfisca_tasks/lint.mk +++ b/openfisca_tasks/lint.mk @@ -9,7 +9,7 @@ check-syntax-errors: . @$(call print_pass,$@:) ## Run linters to check for syntax and style errors. -check-style: $(shell git ls-files "*.py") +check-style: $(shell git ls-files "*.py" "*.pyi") @$(call print_help,$@:) @isort --check $? @black --check $? @@ -32,21 +32,23 @@ lint-doc-%: @## able to integrate documentation improvements progresively. @## @$(call print_help,$(subst $*,%,$@:)) - @flake8 --select=D101,D102,D103,DAR openfisca_core/$* - @pylint openfisca_core/$* + @flake8 --select=D101,D102,D103,DAR openfisca_core/$* openfisca_test + @pylint openfisca_core/$* openfisca_test @$(call print_pass,$@:) ## Run static type checkers for type errors. check-types: @$(call print_help,$@:) + @command -v pyright && pyright @mypy \ openfisca_core/commons \ openfisca_core/entities \ - openfisca_core/types.py + openfisca_core/types.py \ + openfisca_test @$(call print_pass,$@:) ## Run code formatters to correct style errors. -format-style: $(shell git ls-files "*.py") +format-style: $(shell git ls-files "*.py" "*.pyi") @$(call print_help,$@:) @isort $? @black $? diff --git a/openfisca_tasks/test_code.mk b/openfisca_tasks/test_code.mk index 723c94ece0..14fa8736f7 100644 --- a/openfisca_tasks/test_code.mk +++ b/openfisca_tasks/test_code.mk @@ -36,7 +36,8 @@ test-core: $(shell pytest --quiet --quiet --collect-only 2> /dev/null | cut -f 1 openfisca_core/entities \ openfisca_core/holders \ openfisca_core/periods \ - openfisca_core/projectors + openfisca_core/projectors \ + openfisca_test @PYTEST_ADDOPTS="$${PYTEST_ADDOPTS} ${pytest_args}" \ coverage run -m \ ${openfisca} test $? \ diff --git a/openfisca_test/__init__.py b/openfisca_test/__init__.py new file mode 100644 index 0000000000..14707e1e11 --- /dev/null +++ b/openfisca_test/__init__.py @@ -0,0 +1,5 @@ +from . import types +from ._assertions import assert_near +from ._parsers import parse + +__all__ = ["assert_near", "parse", "types"] diff --git a/openfisca_test/_assertions.py b/openfisca_test/_assertions.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openfisca_test/_parsers.py b/openfisca_test/_parsers.py new file mode 100644 index 0000000000..db12702fa0 --- /dev/null +++ b/openfisca_test/_parsers.py @@ -0,0 +1,245 @@ +from typing import NoReturn, Union +from typing_extensions import TypeAlias + +import datetime +import functools + +import numpy +import pendulum + +from openfisca_core import indexed_enums as enum +from openfisca_core import periods, tools + +from . import types as t + +ReturnType: TypeAlias = Union[ + NoReturn, + enum.EnumArray, + t.Array[t.ArrayDate], + t.Array[t.ArrayBool], + t.Array[t.ArrayInt], + t.Array[t.ArrayFloat], + t.Array[t.ArrayBytes], +] + + +class _ListEnum(list[enum.Enum]): + ... + + +class _ListDate(list[datetime.date]): + ... + + +class _ListBool(list[bool]): + ... + + +class _ListInt(list[int]): + ... + + +class _ListFloat(list[float]): + ... + + +class _ListStr(list[str]): + ... + + +@functools.singledispatch +def parse(__: object) -> ReturnType: + """Parse a value to a numpy array. + + Args: + __: Value to parse. + + Returns: + Parsed value. + + Examples: + >>> from openfisca_core import indexed_enums as enum, periods + + >>> class TestEnum(enum.Enum): + ... tenant = "tenant" + ... owner = "owner" + + >>> period = periods.period(2024) + >>> instant = period.start + >>> date = instant.date + + >>> parse(TestEnum) + EnumArray([ ]) + + >>> parse(TestEnum.tenant) + EnumArray([]) + + >>> parse(period) + Traceback (most recent call last): + NotImplementedError + + >>> parse(instant) + array(['2024-01-01'], dtype='datetime64[D]') + + >>> parse(date) + array(['2024-01-01'], dtype='datetime64[D]') + + >>> parse(True) + array([ True]) + + >>> parse(1) + array([1], dtype=int32) + + >>> parse(1.0) + array([1.], dtype=float32) + + >>> parse("TestEnum") + array([b'TestEnum'], dtype='|S8') + + >>> parse("2024-01-01") + Traceback (most recent call last): + SyntaxError: leading zeros in decimal integer literals are not permi... + + >>> parse("True") + array([ True]) + + >>> parse("1") + array([1], dtype=int32) + + >>> parse("1.0") + array([1.], dtype=float32) + + >>> parse("parent") + array([b'parent'], dtype='|S6') + + """ + + if isinstance(__, numpy.ndarray) and __.ndim == 0: + return parse(__.item()) + if isinstance(__, numpy.ndarray): + if numpy.issubdtype(__.dtype, numpy.bool_): + return __.astype(t.ArrayBool) + if numpy.issubdtype(__.dtype, numpy.int_): + return __.astype(t.ArrayInt) + if numpy.issubdtype(__.dtype, numpy.float_): + return __.astype(t.ArrayInt) + for el in __: + if isinstance(el, numpy.ndarray): + return parse(el) + if isinstance(__, (list, tuple, numpy.ndarray)): + if isinstance(__[0], enum.Enum.__class__): + return parse(_ListEnum(el for el in __[0])) + if isinstance(__[0], enum.Enum): + return parse(_ListEnum(__)) + if isinstance(__[0], periods.Instant): + return parse(_ListDate(el.date for el in __)) + if isinstance(__[0], datetime.date): + return parse(_ListDate(__)) + if isinstance(__[0], pendulum.Date): + return parse(_ListDate(__)) + if isinstance(__[0], bool): + return parse(_ListBool(__)) + if isinstance(__[0], int): + return parse(_ListInt(__)) + if isinstance(__[0], float): + return parse(_ListFloat(__)) + if all(isinstance(el, str) for el in __): + return parse(_ListStr(__)) + raise NotImplementedError + + +@parse.register +def _(__: enum.Enum.__class__) -> ReturnType: + return parse(_ListEnum([el for el in __])) + + +@parse.register +def _(__: enum.Enum) -> ReturnType: + return parse(_ListEnum([__])) + + +@parse.register +def _(__: periods.Instant) -> ReturnType: + return parse(_ListDate([__.date])) + + +@parse.register +def _(__: datetime.date) -> ReturnType: + return parse(_ListDate([__])) + + +@parse.register +def _(__: pendulum.Date) -> ReturnType: + return parse(_ListDate([__])) + + +@parse.register +def _(__: bool) -> ReturnType: + return parse(_ListBool([__])) + + +@parse.register +def _(__: int) -> ReturnType: + return parse(_ListInt([__])) + + +@parse.register +def _(__: float) -> ReturnType: + return parse(_ListFloat([__])) + + +@parse.register +def _(__: str) -> ReturnType: + return parse(_ListStr([__])) + + +@parse.register +def _(__: _ListEnum) -> enum.EnumArray: + index = [el.index for el in __] + array = numpy.array(index, dtype=t.ArrayEnum) + return enum.EnumArray(array, __[0].__class__) + + +@parse.register +def _(__: _ListDate) -> t.Array[t.ArrayDate]: + return numpy.array(__, dtype=t.ArrayDate) + + +@parse.register +def _(__: _ListBool) -> t.Array[t.ArrayBool]: + return numpy.array(__, dtype=t.ArrayBool) + + +@parse.register +def _(__: _ListInt) -> t.Array[t.ArrayInt]: + return numpy.array(__, dtype=t.ArrayInt) + + +@parse.register +def _(__: _ListFloat) -> t.Array[t.ArrayFloat]: + return numpy.array(__, dtype=t.ArrayFloat) + + +@parse.register +def _( + __: _ListStr, +) -> Union[ + t.Array[t.ArrayBool], + t.Array[t.ArrayInt], + t.Array[t.ArrayFloat], + t.Array[t.ArrayBytes], +]: + if len(__) == 1: + scalar = tools.eval_expression(__[0]) + if isinstance(scalar, str): + return numpy.array([scalar], dtype=t.ArrayBytes) + value = scalar.item() + if isinstance(value, bool): + return numpy.array([value], dtype=t.ArrayBool) + if isinstance(value, int): + return numpy.array([value], dtype=t.ArrayInt) + return numpy.array([value], dtype=t.ArrayFloat) + return numpy.array(__, dtype=t.ArrayBytes) + + +__all__ = ["parse"] diff --git a/openfisca_test/tests/__init__.py b/openfisca_test/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/tools/test_assert_near.py b/openfisca_test/tests/test_assert_near.py similarity index 100% rename from tests/core/tools/test_assert_near.py rename to openfisca_test/tests/test_assert_near.py diff --git a/openfisca_test/tests/test_parse.py b/openfisca_test/tests/test_parse.py new file mode 100644 index 0000000000..af74c2ed70 --- /dev/null +++ b/openfisca_test/tests/test_parse.py @@ -0,0 +1,58 @@ +import datetime + +import numpy +import pytest + +import openfisca_test as test +from openfisca_core import indexed_enums as enum +from openfisca_core import periods + +date = numpy.array(["2024-01-01"], dtype="datetime64[D]") + + +class TestEnum(enum.Enum): + tenant = "tenant" + owner = "owner" + + +def value(value): + return value + + +def sequence(value): + return [value] + + +def scalar(value): + return numpy.array(value) + + +def array(value): + return numpy.array([value]) + + +def enum_array(value): + return TestEnum.encode(numpy.array(value)) + + +@pytest.mark.parametrize("f", [value, sequence, scalar, array]) +@pytest.mark.parametrize( + "arg, expected", + [ + (TestEnum, enum_array(["tenant", "owner"])), + (TestEnum.tenant, enum_array(["tenant"])), + (periods.Instant((2024, 1, 1)).date, date), + (datetime.date(2024, 1, 1), date), + (True, numpy.array([True])), + (1, numpy.array([1], dtype=numpy.int32)), + (1.0, numpy.array([1.0], dtype=numpy.float32)), + ("TestEnum", numpy.array([b"TestEnum"], dtype="|S8")), + ("True", numpy.array([True])), + ("1", numpy.array([1], dtype=numpy.int32)), + ("1.0", numpy.array([1.0], dtype=numpy.float32)), + ("parent", numpy.array([b"parent"], dtype="|S6")), + ], +) +def test_parse(f, arg, expected): + actual = test.parse(f(arg)) + assert (actual == expected).all() diff --git a/openfisca_test/types.py b/openfisca_test/types.py new file mode 100644 index 0000000000..a473d0622e --- /dev/null +++ b/openfisca_test/types.py @@ -0,0 +1,21 @@ +from openfisca_core.types import ( + Array, + ArrayBool, + ArrayBytes, + ArrayDate, + ArrayEnum, + ArrayFloat, + ArrayInt, + ArrayObject, +) + +__all__ = [ + "Array", + "ArrayBool", + "ArrayDate", + "ArrayBytes", + "ArrayEnum", + "ArrayFloat", + "ArrayInt", + "ArrayObject", +] diff --git a/setup.cfg b/setup.cfg index 596ce99153..958627b18b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -13,7 +13,13 @@ docstring_style = google extend-ignore = D ignore = B019, E203, E501, F405, E701, E704, RST212, RST301, W503 in-place = true -include-in-doctest = openfisca_core/commons openfisca_core/entities openfisca_core/holders openfisca_core/periods openfisca_core/projectors +include-in-doctest = + openfisca_core/commons + openfisca_core/entities + openfisca_core/holders + openfisca_core/periods + openfisca_core/projectors + openfisca_test max-line-length = 88 per-file-ignores = */types.py:D101,D102,E704 @@ -31,8 +37,10 @@ load-plugins = pylint_per_file_ignores disable = all enable = C0115, C0116, R0401 per-file-ignores = - types.py:C0115,C0116 - /tests/:C0116 + */types.py:D101,D102,E704 + */test_*.py:D101,D102,D103 + */__init__.py:F401 + */__init__.pyi:E302,E704 score = no [isort] @@ -42,7 +50,7 @@ force_alphabetical_sort_within_sections = false group_by_package = true honor_noqa = true include_trailing_comma = true -known_first_party = openfisca_core +known_first_party = openfisca_core, openfisca_test known_openfisca = openfisca_country_template, openfisca_extension_template known_typing = *collections.abc*, *typing*, *typing_extensions* known_types = *types* @@ -56,7 +64,7 @@ source = . */site-packages [coverage:run] branch = true -source = openfisca_core, openfisca_web_api +source = openfisca_core, openfisca_test, openfisca_web_api [coverage:report] fail_under = 75 diff --git a/setup.py b/setup.py index 2f1c7089bf..64dae0774b 100644 --- a/setup.py +++ b/setup.py @@ -29,7 +29,7 @@ general_requirements = [ "PyYAML >=6.0, <7.0", "dpath >=2.1.4, <3.0", - "numexpr >=2.8.4, <3.0", + "numexpr >=2.10.1, <3.0", "numpy >=1.24.2, <1.25", "pendulum >=2.1.2, <3.0.0", "psutil >=5.9.4, <6.0", diff --git a/stubs/numexpr/__init__.pyi b/stubs/numexpr/__init__.pyi new file mode 100644 index 0000000000..eeca098e9d --- /dev/null +++ b/stubs/numexpr/__init__.pyi @@ -0,0 +1,13 @@ +from numpy.typing import NDArray as Array +from typing import NoReturn +from typing_extensions import TypeAlias + +import numpy + +ArrayBool: TypeAlias = numpy.bool_ +ArrayInt: TypeAlias = numpy.int32 +ArrayFloat: TypeAlias = numpy.float32 + +def evaluate( + __ex: str, *__args: object, **__kwargs: object +) -> NoReturn | Array[ArrayBool | ArrayInt | ArrayFloat]: ... diff --git a/stubs/pendulum/__init__.pyi b/stubs/pendulum/__init__.pyi new file mode 100644 index 0000000000..67aea19e46 --- /dev/null +++ b/stubs/pendulum/__init__.pyi @@ -0,0 +1,3 @@ +from pendulum.date import Date + +__all__ = ["Date"] diff --git a/tests/core/parameters_date_indexing/test_date_indexing.py b/tests/core/parameters_date_indexing/test_date_indexing.py index cefec26648..a1342b8ceb 100644 --- a/tests/core/parameters_date_indexing/test_date_indexing.py +++ b/tests/core/parameters_date_indexing/test_date_indexing.py @@ -3,7 +3,7 @@ import numpy from openfisca_core.parameters import ParameterNode -from openfisca_core.tools import assert_near +from openfisca_test import assert_near from openfisca_core.model_api import * # noqa diff --git a/tests/core/parameters_fancy_indexing/test_fancy_indexing.py b/tests/core/parameters_fancy_indexing/test_fancy_indexing.py index b7e7cf4e45..03bea5b09e 100644 --- a/tests/core/parameters_fancy_indexing/test_fancy_indexing.py +++ b/tests/core/parameters_fancy_indexing/test_fancy_indexing.py @@ -6,7 +6,7 @@ from openfisca_core.indexed_enums import Enum from openfisca_core.parameters import Parameter, ParameterNode, ParameterNotFound -from openfisca_core.tools import assert_near +from openfisca_test import assert_near LOCAL_DIR = os.path.dirname(os.path.abspath(__file__)) diff --git a/tests/core/tax_scales/test_linear_average_rate_tax_scale.py b/tests/core/tax_scales/test_linear_average_rate_tax_scale.py index 6205d6de9b..13cab611d2 100644 --- a/tests/core/tax_scales/test_linear_average_rate_tax_scale.py +++ b/tests/core/tax_scales/test_linear_average_rate_tax_scale.py @@ -1,7 +1,8 @@ import numpy import pytest -from openfisca_core import taxscales, tools +import openfisca_test as test +from openfisca_core import taxscales def test_bracket_indices() -> None: @@ -13,7 +14,7 @@ def test_bracket_indices() -> None: result = tax_scale.bracket_indices(tax_base) - tools.assert_near(result, [0, 0, 0, 1, 1, 2]) + test.assert_near(result, [0, 0, 0, 1, 1, 2]) def test_bracket_indices_with_factor() -> None: @@ -25,7 +26,7 @@ def test_bracket_indices_with_factor() -> None: result = tax_scale.bracket_indices(tax_base, factor=2.0) - tools.assert_near(result, [0, 0, 0, 0, 1, 1]) + test.assert_near(result, [0, 0, 0, 0, 1, 1]) def test_bracket_indices_with_round_decimals() -> None: @@ -37,7 +38,7 @@ def test_bracket_indices_with_round_decimals() -> None: result = tax_scale.bracket_indices(tax_base, round_decimals=0) - tools.assert_near(result, [0, 0, 1, 1, 2, 2]) + test.assert_near(result, [0, 0, 1, 1, 2, 2]) def test_bracket_indices_without_tax_base() -> None: @@ -79,8 +80,8 @@ def test_to_marginal() -> None: result = tax_scale.to_marginal() assert result.thresholds == [0, 1, 2] - tools.assert_near(result.rates, [0.1, 0.3, 0.2], absolute_error_margin=0) - tools.assert_near( + test.assert_near(result.rates, [0.1, 0.3, 0.2], absolute_error_margin=0) + test.assert_near( result.calc(tax_base), [0.1, 0.25, 0.4, 0.5], absolute_error_margin=0, diff --git a/tests/core/tax_scales/test_marginal_amount_tax_scale.py b/tests/core/tax_scales/test_marginal_amount_tax_scale.py index 0a3275c901..9a74b55d44 100644 --- a/tests/core/tax_scales/test_marginal_amount_tax_scale.py +++ b/tests/core/tax_scales/test_marginal_amount_tax_scale.py @@ -1,7 +1,8 @@ from numpy import array from pytest import fixture -from openfisca_core import parameters, periods, taxscales, tools +import openfisca_test as test +from openfisca_core import parameters, periods, taxscales @fixture @@ -28,7 +29,7 @@ def test_calc() -> None: result = tax_scale.calc(tax_base) - tools.assert_near(result, [0, 0.23, 0.52]) + test.assert_near(result, [0, 0.23, 0.52]) # TODO: move, as we're testing Scale, not MarginalAmountTaxScale diff --git a/tests/core/tax_scales/test_marginal_rate_tax_scale.py b/tests/core/tax_scales/test_marginal_rate_tax_scale.py index 7696e95fc4..4f470ecfbb 100644 --- a/tests/core/tax_scales/test_marginal_rate_tax_scale.py +++ b/tests/core/tax_scales/test_marginal_rate_tax_scale.py @@ -1,7 +1,8 @@ import numpy import pytest -from openfisca_core import taxscales, tools +import openfisca_test as test +from openfisca_core import taxscales def test_bracket_indices() -> None: @@ -13,7 +14,7 @@ def test_bracket_indices() -> None: result = tax_scale.bracket_indices(tax_base) - tools.assert_near(result, [0, 0, 0, 1, 1, 2]) + test.assert_near(result, [0, 0, 0, 1, 1, 2]) def test_bracket_indices_with_factor() -> None: @@ -25,7 +26,7 @@ def test_bracket_indices_with_factor() -> None: result = tax_scale.bracket_indices(tax_base, factor=2.0) - tools.assert_near(result, [0, 0, 0, 0, 1, 1]) + test.assert_near(result, [0, 0, 0, 0, 1, 1]) def test_bracket_indices_with_round_decimals() -> None: @@ -37,7 +38,7 @@ def test_bracket_indices_with_round_decimals() -> None: result = tax_scale.bracket_indices(tax_base, round_decimals=0) - tools.assert_near(result, [0, 0, 1, 1, 2, 2]) + test.assert_near(result, [0, 0, 1, 1, 2, 2]) def test_bracket_indices_without_tax_base() -> None: @@ -79,7 +80,7 @@ def test_calc() -> None: result = tax_scale.calc(tax_base) - tools.assert_near( + test.assert_near( result, [0, 0.05, 0.1, 0.2, 0.3, 0.3], absolute_error_margin=1e-10, @@ -94,7 +95,7 @@ def test_calc_without_round() -> None: result = tax_scale.calc(tax_base) - tools.assert_near( + test.assert_near( result, [10, 10.02, 10.0002, 10.06, 10.0006, 10.05, 10.0005], absolute_error_margin=1e-10, @@ -109,7 +110,7 @@ def test_calc_when_round_is_1() -> None: result = tax_scale.calc(tax_base, round_base_decimals=1) - tools.assert_near( + test.assert_near( result, [10, 10.0, 10.0, 10.1, 10.0, 10, 10.0], absolute_error_margin=1e-10, @@ -124,7 +125,7 @@ def test_calc_when_round_is_2() -> None: result = tax_scale.calc(tax_base, round_base_decimals=2) - tools.assert_near( + test.assert_near( result, [10, 10.02, 10.0, 10.06, 10.00, 10.05, 10], absolute_error_margin=1e-10, @@ -139,7 +140,7 @@ def test_calc_when_round_is_3() -> None: result = tax_scale.calc(tax_base, round_base_decimals=3) - tools.assert_near( + test.assert_near( result, [10, 10.02, 10.0, 10.06, 10.001, 10.05, 10], absolute_error_margin=1e-10, @@ -155,7 +156,7 @@ def test_marginal_rates() -> None: result = tax_scale.marginal_rates(tax_base) - tools.assert_near(result, [0, 0, 0, 0.1, 0.2]) + test.assert_near(result, [0, 0, 0, 0.1, 0.2]) def test_inverse() -> None: @@ -168,7 +169,7 @@ def test_inverse() -> None: result = tax_scale.inverse() - tools.assert_near(result.calc(net_tax_base), gross_tax_base, 1e-15) + test.assert_near(result.calc(net_tax_base), gross_tax_base, 1e-15) def test_scale_tax_scales() -> None: @@ -182,7 +183,7 @@ def test_scale_tax_scales() -> None: result = tax_scale.scale_tax_scales(tax_base_scale) - tools.assert_near(result.thresholds, scaled_tax_base) + test.assert_near(result.thresholds, scaled_tax_base) def test_inverse_scaled_marginal_tax_scales() -> None: @@ -200,7 +201,7 @@ def test_inverse_scaled_marginal_tax_scales() -> None: result = scaled_tax_scale.inverse() - tools.assert_near(result.calc(scaled_net_tax_base), scaled_gross_tax_base, 1e-13) + test.assert_near(result.calc(scaled_net_tax_base), scaled_gross_tax_base, 1e-13) def test_to_average() -> None: @@ -215,7 +216,7 @@ def test_to_average() -> None: # Note: assert_near doesn't work for inf. assert result.thresholds == [0, 1, 2, numpy.inf] assert result.rates, [0, 0, 0.05, 0.2] - tools.assert_near( + test.assert_near( result.calc(tax_base), [0, 0.0375, 0.1, 0.125], absolute_error_margin=1e-10, diff --git a/tests/core/tax_scales/test_single_amount_tax_scale.py b/tests/core/tax_scales/test_single_amount_tax_scale.py index 2b384f6374..9e2212fea2 100644 --- a/tests/core/tax_scales/test_single_amount_tax_scale.py +++ b/tests/core/tax_scales/test_single_amount_tax_scale.py @@ -1,7 +1,8 @@ import numpy from pytest import fixture -from openfisca_core import parameters, periods, taxscales, tools +import openfisca_test as test +from openfisca_core import parameters, periods, taxscales @fixture @@ -32,7 +33,7 @@ def test_calc() -> None: result = tax_scale.calc(tax_base) - tools.assert_near(result, [0, 0.23, 0.29]) + test.assert_near(result, [0, 0.23, 0.29]) def test_to_dict() -> None: diff --git a/tests/core/tax_scales/test_tax_scales_commons.py b/tests/core/tax_scales/test_tax_scales_commons.py index 544e5a07fe..66044e92ab 100644 --- a/tests/core/tax_scales/test_tax_scales_commons.py +++ b/tests/core/tax_scales/test_tax_scales_commons.py @@ -1,6 +1,7 @@ import pytest -from openfisca_core import parameters, taxscales, tools +import openfisca_test as test +from openfisca_core import parameters, taxscales @pytest.fixture @@ -27,5 +28,5 @@ def node(): def test_combine_tax_scales(node) -> None: result = taxscales.combine_tax_scales(node) - tools.assert_near(result.thresholds, [0, 2000, 3000]) - tools.assert_near(result.rates, [0.07, 0.12, 0.14], 1e-13) + test.assert_near(result.thresholds, [0, 2000, 3000]) + test.assert_near(result.rates, [0.07, 0.12, 0.14], 1e-13) diff --git a/tests/core/test_calculate_output.py b/tests/core/test_calculate_output.py index 54d868ba92..bfe3527c79 100644 --- a/tests/core/test_calculate_output.py +++ b/tests/core/test_calculate_output.py @@ -2,7 +2,8 @@ from openfisca_country_template import entities, situation_examples -from openfisca_core import simulations, tools +import openfisca_test as test +from openfisca_core import simulations from openfisca_core.periods import DateUnit from openfisca_core.simulations import SimulationBuilder from openfisca_core.variables import Variable @@ -62,7 +63,7 @@ def test_calculate_output_add(simulation) -> None: def test_calculate_output_divide(simulation) -> None: simulation.set_input("variable_with_calculate_output_divide", 2017, [12000]) - tools.assert_near( + test.assert_near( simulation.calculate_output("variable_with_calculate_output_divide", "2017-06"), 1000, ) diff --git a/tests/core/test_countries.py b/tests/core/test_countries.py index d206a8cb35..146cd0eada 100644 --- a/tests/core/test_countries.py +++ b/tests/core/test_countries.py @@ -1,6 +1,7 @@ import pytest -from openfisca_core import periods, populations, tools +import openfisca_test as test +from openfisca_core import periods, populations from openfisca_core.errors import VariableNameConflictError, VariableNotFoundError from openfisca_core.periods import DateUnit from openfisca_core.simulations import SimulationBuilder @@ -12,19 +13,19 @@ @pytest.mark.parametrize("simulation", [({"salary": 2000}, PERIOD)], indirect=True) def test_input_variable(simulation) -> None: result = simulation.calculate("salary", PERIOD) - tools.assert_near(result, [2000], absolute_error_margin=0.01) + test.assert_near(result, [2000], absolute_error_margin=0.01) @pytest.mark.parametrize("simulation", [({"salary": 2000}, PERIOD)], indirect=True) def test_basic_calculation(simulation) -> None: result = simulation.calculate("income_tax", PERIOD) - tools.assert_near(result, [300], absolute_error_margin=0.01) + test.assert_near(result, [300], absolute_error_margin=0.01) @pytest.mark.parametrize("simulation", [({"salary": 24000}, PERIOD)], indirect=True) def test_calculate_add(simulation) -> None: result = simulation.calculate_add("income_tax", PERIOD) - tools.assert_near(result, [3600], absolute_error_margin=0.01) + test.assert_near(result, [3600], absolute_error_margin=0.01) @pytest.mark.parametrize( @@ -34,14 +35,14 @@ def test_calculate_add(simulation) -> None: ) def test_calculate_divide(simulation) -> None: result = simulation.calculate_divide("housing_tax", PERIOD) - tools.assert_near(result, [1000 / 12.0], absolute_error_margin=0.01) + test.assert_near(result, [1000 / 12.0], absolute_error_margin=0.01) @pytest.mark.parametrize("simulation", [({"salary": 20000}, PERIOD)], indirect=True) def test_bareme(simulation) -> None: result = simulation.calculate("social_security_contribution", PERIOD) expected = [0.02 * 6000 + 0.06 * 6400 + 0.12 * 7600] - tools.assert_near(result, expected, absolute_error_margin=0.01) + test.assert_near(result, expected, absolute_error_margin=0.01) @pytest.mark.parametrize("simulation", [({}, PERIOD)], indirect=True) diff --git a/tests/core/test_cycles.py b/tests/core/test_cycles.py index acb08c6424..62005db141 100644 --- a/tests/core/test_cycles.py +++ b/tests/core/test_cycles.py @@ -2,7 +2,8 @@ from openfisca_country_template import entities -from openfisca_core import periods, tools +import openfisca_test as test +from openfisca_core import periods from openfisca_core.errors import CycleError from openfisca_core.periods import DateUnit from openfisca_core.simulations import SimulationBuilder @@ -122,7 +123,7 @@ def test_pure_cycle(simulation, reference_period) -> None: def test_spirals_result_in_default_value(simulation, reference_period) -> None: variable3 = simulation.calculate("variable3", period=reference_period) - tools.assert_near(variable3, [0]) + test.assert_near(variable3, [0]) def test_spiral_heuristic(simulation, reference_period) -> None: @@ -132,9 +133,9 @@ def test_spiral_heuristic(simulation, reference_period) -> None: "variable6", reference_period.last_month, ) - tools.assert_near(variable5, [11]) - tools.assert_near(variable6, [11]) - tools.assert_near(variable6_last_month, [11]) + test.assert_near(variable5, [11]) + test.assert_near(variable6, [11]) + test.assert_near(variable6_last_month, [11]) def test_spiral_cache(simulation, reference_period) -> None: @@ -146,4 +147,4 @@ def test_spiral_cache(simulation, reference_period) -> None: def test_cotisation_1_level(simulation, reference_period) -> None: month = reference_period.last_month cotisation = simulation.calculate("cotisation", period=month) - tools.assert_near(cotisation, [0]) + test.assert_near(cotisation, [0]) diff --git a/tests/core/test_entities.py b/tests/core/test_entities.py index aba17dc4dc..2f00d940d7 100644 --- a/tests/core/test_entities.py +++ b/tests/core/test_entities.py @@ -2,7 +2,7 @@ from openfisca_country_template import entities, situation_examples -from openfisca_core import tools +import openfisca_test as test from openfisca_core.simulations import SimulationBuilder from openfisca_core.tools import test_runner @@ -36,12 +36,12 @@ def new_simulation(tax_benefit_system, test_case, period=MONTH): def test_role_index_and_positions(tax_benefit_system) -> None: simulation = new_simulation(tax_benefit_system, TEST_CASE) - tools.assert_near(simulation.household.members_entity_id, [0, 0, 0, 0, 1, 1]) + test.assert_near(simulation.household.members_entity_id, [0, 0, 0, 0, 1, 1]) assert ( simulation.household.members_role == [FIRST_PARENT, SECOND_PARENT, CHILD, CHILD, FIRST_PARENT, CHILD] ).all() - tools.assert_near(simulation.household.members_position, [0, 1, 2, 3, 0, 1]) + test.assert_near(simulation.household.members_position, [0, 1, 2, 3, 0, 1]) assert simulation.person.ids == ["ind0", "ind1", "ind2", "ind3", "ind4", "ind5"] assert simulation.household.ids == ["h1", "h2"] @@ -74,12 +74,12 @@ def test_entity_structure_with_constructor(tax_benefit_system) -> None: household = simulation.household - tools.assert_near(household.members_entity_id, [0, 0, 1, 0, 0]) + test.assert_near(household.members_entity_id, [0, 0, 1, 0, 0]) assert ( household.members_role == [FIRST_PARENT, SECOND_PARENT, FIRST_PARENT, CHILD, CHILD] ).all() - tools.assert_near(household.members_position, [0, 1, 0, 2, 3]) + test.assert_near(household.members_position, [0, 1, 0, 2, 3]) def test_entity_variables_with_constructor(tax_benefit_system) -> None: @@ -112,7 +112,7 @@ def test_entity_variables_with_constructor(tax_benefit_system) -> None: test_runner.yaml.safe_load(simulation_yaml), ) household = simulation.household - tools.assert_near(household("rent", "2017-06"), [800, 600]) + test.assert_near(household("rent", "2017-06"), [800, 600]) def test_person_variable_with_constructor(tax_benefit_system) -> None: @@ -148,8 +148,8 @@ def test_person_variable_with_constructor(tax_benefit_system) -> None: test_runner.yaml.safe_load(simulation_yaml), ) person = simulation.person - tools.assert_near(person("salary", "2017-11"), [1500, 0, 3000, 0, 0]) - tools.assert_near(person("salary", "2017-12"), [2000, 0, 4000, 0, 0]) + test.assert_near(person("salary", "2017-11"), [1500, 0, 3000, 0, 0]) + test.assert_near(person("salary", "2017-12"), [2000, 0, 4000, 0, 0]) def test_set_input_with_constructor(tax_benefit_system) -> None: @@ -190,14 +190,14 @@ def test_set_input_with_constructor(tax_benefit_system) -> None: test_runner.yaml.safe_load(simulation_yaml), ) person = simulation.person - tools.assert_near(person("salary", "2017-12"), [2000, 0, 4000, 0, 0]) - tools.assert_near(person("salary", "2017-10"), [2000, 3000, 1600, 0, 0]) + test.assert_near(person("salary", "2017-12"), [2000, 0, 4000, 0, 0]) + test.assert_near(person("salary", "2017-10"), [2000, 3000, 1600, 0, 0]) def test_has_role(tax_benefit_system) -> None: simulation = new_simulation(tax_benefit_system, TEST_CASE) individu = simulation.persons - tools.assert_near(individu.has_role(CHILD), [False, False, True, True, False, True]) + test.assert_near(individu.has_role(CHILD), [False, False, True, True, False, True]) def test_has_role_with_subrole(tax_benefit_system) -> None: @@ -207,7 +207,7 @@ def test_has_role_with_subrole(tax_benefit_system) -> None: individu.has_role(PARENT), [True, True, False, False, True, False], ) - tools.assert_near( + test.assert_near( individu.has_role(FIRST_PARENT), [True, False, False, False, True, False], ) @@ -227,10 +227,10 @@ def test_project(tax_benefit_system) -> None: housing_tax = household("housing_tax", YEAR) projected_housing_tax = household.project(housing_tax) - tools.assert_near(projected_housing_tax, [20000, 20000, 20000, 20000, 0, 0]) + test.assert_near(projected_housing_tax, [20000, 20000, 20000, 20000, 0, 0]) housing_tax_projected_on_parents = household.project(housing_tax, role=PARENT) - tools.assert_near(housing_tax_projected_on_parents, [20000, 20000, 0, 0, 0, 0]) + test.assert_near(housing_tax_projected_on_parents, [20000, 20000, 0, 0, 0, 0]) def test_implicit_projection(tax_benefit_system) -> None: @@ -241,7 +241,7 @@ def test_implicit_projection(tax_benefit_system) -> None: individu = simulation.person housing_tax = individu.household("housing_tax", YEAR) - tools.assert_near(housing_tax, [20000, 20000, 20000, 20000, 0, 0]) + test.assert_near(housing_tax, [20000, 20000, 20000, 20000, 0, 0]) def test_sum(tax_benefit_system) -> None: @@ -257,11 +257,11 @@ def test_sum(tax_benefit_system) -> None: salary = household.members("salary", "2016-01") total_salary_by_household = household.sum(salary) - tools.assert_near(total_salary_by_household, [2500, 3500]) + test.assert_near(total_salary_by_household, [2500, 3500]) total_salary_parents_by_household = household.sum(salary, role=PARENT) - tools.assert_near(total_salary_parents_by_household, [2500, 3000]) + test.assert_near(total_salary_parents_by_household, [2500, 3000]) def test_any(tax_benefit_system) -> None: @@ -272,11 +272,11 @@ def test_any(tax_benefit_system) -> None: age = household.members("age", period=MONTH) condition_age = age <= 18 has_household_member_with_age_inf_18 = household.any(condition_age) - tools.assert_near(has_household_member_with_age_inf_18, [True, False]) + test.assert_near(has_household_member_with_age_inf_18, [True, False]) condition_age_2 = age > 18 has_household_CHILD_with_age_sup_18 = household.any(condition_age_2, role=CHILD) - tools.assert_near(has_household_CHILD_with_age_sup_18, [False, True]) + test.assert_near(has_household_CHILD_with_age_sup_18, [False, True]) def test_all(tax_benefit_system) -> None: @@ -288,10 +288,10 @@ def test_all(tax_benefit_system) -> None: condition_age = age >= 18 all_persons_age_sup_18 = household.all(condition_age) - tools.assert_near(all_persons_age_sup_18, [False, True]) + test.assert_near(all_persons_age_sup_18, [False, True]) all_parents_age_sup_18 = household.all(condition_age, role=PARENT) - tools.assert_near(all_parents_age_sup_18, [True, True]) + test.assert_near(all_parents_age_sup_18, [True, True]) def test_max(tax_benefit_system) -> None: @@ -302,10 +302,10 @@ def test_max(tax_benefit_system) -> None: age = household.members("age", period=MONTH) age_max = household.max(age) - tools.assert_near(age_max, [40, 54]) + test.assert_near(age_max, [40, 54]) age_max_child = household.max(age, role=CHILD) - tools.assert_near(age_max_child, [9, 20]) + test.assert_near(age_max_child, [9, 20]) def test_min(tax_benefit_system) -> None: @@ -316,10 +316,10 @@ def test_min(tax_benefit_system) -> None: age = household.members("age", period=MONTH) age_min = household.min(age) - tools.assert_near(age_min, [7, 20]) + test.assert_near(age_min, [7, 20]) age_min_parents = household.min(age, role=PARENT) - tools.assert_near(age_min_parents, [37, 54]) + test.assert_near(age_min_parents, [37, 54]) def test_value_nth_person(tax_benefit_system) -> None: @@ -329,16 +329,16 @@ def test_value_nth_person(tax_benefit_system) -> None: array = household.members("age", MONTH) result0 = household.value_nth_person(0, array, default=-1) - tools.assert_near(result0, [40, 54]) + test.assert_near(result0, [40, 54]) result1 = household.value_nth_person(1, array, default=-1) - tools.assert_near(result1, [37, 20]) + test.assert_near(result1, [37, 20]) result2 = household.value_nth_person(2, array, default=-1) - tools.assert_near(result2, [7, -1]) + test.assert_near(result2, [7, -1]) result3 = household.value_nth_person(3, array, default=-1) - tools.assert_near(result3, [9, -1]) + test.assert_near(result3, [9, -1]) def test_rank(tax_benefit_system) -> None: @@ -348,14 +348,14 @@ def test_rank(tax_benefit_system) -> None: age = person("age", MONTH) # [40, 37, 7, 9, 54, 20] rank = person.get_rank(person.household, age) - tools.assert_near(rank, [3, 2, 0, 1, 1, 0]) + test.assert_near(rank, [3, 2, 0, 1, 1, 0]) rank_in_siblings = person.get_rank( person.household, -age, condition=person.has_role(entities.Household.CHILD), ) - tools.assert_near(rank_in_siblings, [-1, -1, 1, 0, -1, 0]) + test.assert_near(rank_in_siblings, [-1, -1, 1, 0, -1, 0]) def test_partner(tax_benefit_system) -> None: @@ -372,7 +372,7 @@ def test_partner(tax_benefit_system) -> None: salary_second_parent = persons.value_from_partner(salary, persons.household, PARENT) - tools.assert_near(salary_second_parent, [1500, 1000, 0, 0, 0, 0]) + test.assert_near(salary_second_parent, [1500, 1000, 0, 0, 0, 0]) def test_value_from_first_person(tax_benefit_system) -> None: @@ -388,7 +388,7 @@ def test_value_from_first_person(tax_benefit_system) -> None: salaries = household.members("salary", period=MONTH) salary_first_person = household.value_from_first_person(salaries) - tools.assert_near(salary_first_person, [1000, 3000]) + test.assert_near(salary_first_person, [1000, 3000]) def test_projectors_methods(tax_benefit_system) -> None: @@ -432,7 +432,7 @@ def test_sum_following_bug_ipp_1(tax_benefit_system) -> None: eligible_i = household.members("salary", period=MONTH) < 1500 nb_eligibles_by_household = household.sum(eligible_i, role=CHILD) - tools.assert_near(nb_eligibles_by_household, [0, 2]) + test.assert_near(nb_eligibles_by_household, [0, 2]) def test_sum_following_bug_ipp_2(tax_benefit_system) -> None: @@ -454,7 +454,7 @@ def test_sum_following_bug_ipp_2(tax_benefit_system) -> None: eligible_i = household.members("salary", period=MONTH) < 1500 nb_eligibles_by_household = household.sum(eligible_i, role=CHILD) - tools.assert_near(nb_eligibles_by_household, [2, 0]) + test.assert_near(nb_eligibles_by_household, [2, 0]) def test_get_memory_usage(tax_benefit_system) -> None: @@ -504,47 +504,47 @@ def test_unordered_persons(tax_benefit_system) -> None: # Aggregation/Projection persons -> entity - tools.assert_near(household.sum(salary), [2520, 3500]) - tools.assert_near(household.max(salary), [1500, 3000]) - tools.assert_near(household.min(salary), [0, 500]) - tools.assert_near(household.all(salary > 0), [False, True]) - tools.assert_near(household.any(salary > 2000), [False, True]) - tools.assert_near(household.first_person("salary", "2016-01"), [0, 3000]) - tools.assert_near(household.first_parent("salary", "2016-01"), [1000, 3000]) - tools.assert_near(household.second_parent("salary", "2016-01"), [1500, 0]) - tools.assert_near( + test.assert_near(household.sum(salary), [2520, 3500]) + test.assert_near(household.max(salary), [1500, 3000]) + test.assert_near(household.min(salary), [0, 500]) + test.assert_near(household.all(salary > 0), [False, True]) + test.assert_near(household.any(salary > 2000), [False, True]) + test.assert_near(household.first_person("salary", "2016-01"), [0, 3000]) + test.assert_near(household.first_parent("salary", "2016-01"), [1000, 3000]) + test.assert_near(household.second_parent("salary", "2016-01"), [1500, 0]) + test.assert_near( person.value_from_partner(salary, person.household, PARENT), [0, 0, 1000, 0, 0, 1500], ) - tools.assert_near(household.sum(salary, role=PARENT), [2500, 3000]) - tools.assert_near(household.sum(salary, role=CHILD), [20, 500]) - tools.assert_near(household.max(salary, role=PARENT), [1500, 3000]) - tools.assert_near(household.max(salary, role=CHILD), [20, 500]) - tools.assert_near(household.min(salary, role=PARENT), [1000, 3000]) - tools.assert_near(household.min(salary, role=CHILD), [0, 500]) - tools.assert_near(household.all(salary > 0, role=PARENT), [True, True]) - tools.assert_near(household.all(salary > 0, role=CHILD), [False, True]) - tools.assert_near(household.any(salary < 1500, role=PARENT), [True, False]) - tools.assert_near(household.any(salary > 200, role=CHILD), [False, True]) + test.assert_near(household.sum(salary, role=PARENT), [2500, 3000]) + test.assert_near(household.sum(salary, role=CHILD), [20, 500]) + test.assert_near(household.max(salary, role=PARENT), [1500, 3000]) + test.assert_near(household.max(salary, role=CHILD), [20, 500]) + test.assert_near(household.min(salary, role=PARENT), [1000, 3000]) + test.assert_near(household.min(salary, role=CHILD), [0, 500]) + test.assert_near(household.all(salary > 0, role=PARENT), [True, True]) + test.assert_near(household.all(salary > 0, role=CHILD), [False, True]) + test.assert_near(household.any(salary < 1500, role=PARENT), [True, False]) + test.assert_near(household.any(salary > 200, role=CHILD), [False, True]) # nb_persons - tools.assert_near(household.nb_persons(), [4, 2]) - tools.assert_near(household.nb_persons(role=PARENT), [2, 1]) - tools.assert_near(household.nb_persons(role=CHILD), [2, 1]) + test.assert_near(household.nb_persons(), [4, 2]) + test.assert_near(household.nb_persons(role=PARENT), [2, 1]) + test.assert_near(household.nb_persons(role=CHILD), [2, 1]) # Projection entity -> persons - tools.assert_near( - household.project(accommodation_size), + test.assert_near( + household.project(accommodation_size), [60, 160, 160, 160, 60, 160] [60, 160, 160, 160, 60, 160], ) - tools.assert_near( - household.project(accommodation_size, role=PARENT), + test.assert_near( + household.project(accommodation_size, role=PARENT), [60, 0, 160, 0, 0, 160] [60, 0, 160, 0, 0, 160], ) - tools.assert_near( - household.project(accommodation_size, role=CHILD), + test.assert_near( + household.project(accommodation_size, role=CHILD), [0, 160, 0, 160, 60, 0] [0, 160, 0, 160, 60, 0], ) diff --git a/tests/core/test_holders.py b/tests/core/test_holders.py index c72d053ad6..7240e2741e 100644 --- a/tests/core/test_holders.py +++ b/tests/core/test_holders.py @@ -4,7 +4,8 @@ from openfisca_country_template import situation_examples from openfisca_country_template.variables import housing -from openfisca_core import holders, periods, tools +import openfisca_test as test +from openfisca_core import holders, periods from openfisca_core.errors import PeriodMismatchError from openfisca_core.holders import Holder from openfisca_core.memory_config import MemoryConfig @@ -204,7 +205,7 @@ def test_cache_disk(couple) -> None: data = numpy.asarray([2000, 3000]) holder.put_in_cache(data, month) stored_data = holder.get_array(month) - tools.assert_near(data, stored_data) + test.assert_near(data, stored_data) def test_known_periods(couple) -> None: diff --git a/tests/core/test_reforms.py b/tests/core/test_reforms.py index 1f31bcde2a..794ba4e6c4 100644 --- a/tests/core/test_reforms.py +++ b/tests/core/test_reforms.py @@ -8,8 +8,8 @@ from openfisca_core.parameters import ParameterNode, ValuesHistory from openfisca_core.periods import DateUnit, Instant from openfisca_core.reforms import Reform -from openfisca_core.tools import assert_near from openfisca_core.variables import Variable +from openfisca_test import assert_near class goes_to_school(Variable): diff --git a/tests/core/test_simulation_builder.py b/tests/core/test_simulation_builder.py index b905b29b84..ae4478f90e 100644 --- a/tests/core/test_simulation_builder.py +++ b/tests/core/test_simulation_builder.py @@ -6,7 +6,7 @@ from openfisca_country_template import entities, situation_examples -from openfisca_core import tools +import openfisca_test as test from openfisca_core.errors import SituationParsingError from openfisca_core.indexed_enums import Enum from openfisca_core.periods import DateUnit @@ -117,7 +117,7 @@ def test_add_person_entity_with_values(persons) -> None: persons_json = {"Alicia": {"salary": {"2018-11": 3000}}, "Javier": {}} simulation_builder = SimulationBuilder() simulation_builder.add_person_entity(persons, persons_json) - tools.assert_near(simulation_builder.get_input("salary", "2018-11"), [3000, 0]) + test.assert_near(simulation_builder.get_input("salary", "2018-11"), [3000, 0]) def test_add_person_values_with_default_period(persons) -> None: @@ -125,7 +125,7 @@ def test_add_person_values_with_default_period(persons) -> None: simulation_builder = SimulationBuilder() simulation_builder.set_default_period("2018-11") simulation_builder.add_person_entity(persons, persons_json) - tools.assert_near(simulation_builder.get_input("salary", "2018-11"), [3000, 0]) + test.assert_near(simulation_builder.get_input("salary", "2018-11"), [3000, 0]) def test_add_person_values_with_default_period_old_syntax(persons) -> None: @@ -133,7 +133,7 @@ def test_add_person_values_with_default_period_old_syntax(persons) -> None: simulation_builder = SimulationBuilder() simulation_builder.set_default_period("month:2018-11") simulation_builder.add_person_entity(persons, persons_json) - tools.assert_near(simulation_builder.get_input("salary", "2018-11"), [3000, 0]) + test.assert_near(simulation_builder.get_input("salary", "2018-11"), [3000, 0]) def test_add_group_entity(households) -> None: @@ -329,7 +329,7 @@ def test_finalize_person_entity(persons) -> None: simulation_builder.add_person_entity(persons, persons_json) population = Population(persons) simulation_builder.finalize_variables_init(population) - tools.assert_near(population.get_holder("salary").get_array("2018-11"), [3000, 0]) + test.assert_near(population.get_holder("salary").get_array("2018-11"), [3000, 0]) assert population.count == 2 assert population.ids == ["Alicia", "Javier"] @@ -340,7 +340,7 @@ def test_canonicalize_period_keys(persons) -> None: simulation_builder.add_person_entity(persons, persons_json) population = Population(persons) simulation_builder.finalize_variables_init(population) - tools.assert_near(population.get_holder("salary").get_array("2018-12"), [100]) + test.assert_near(population.get_holder("salary").get_array("2018-12"), [100]) def test_finalize_households(tax_benefit_system) -> None: @@ -359,8 +359,8 @@ def test_finalize_households(tax_benefit_system) -> None: }, ) simulation_builder.finalize_variables_init(simulation.household) - tools.assert_near(simulation.household.members_entity_id, [0, 0, 1, 1]) - tools.assert_near( + test.assert_near(simulation.household.members_entity_id, [0, 0, 1, 1]) + test.assert_near( simulation.persons.has_role(entities.Household.PARENT), [True, True, False, True], ) @@ -639,7 +639,7 @@ def test_vectorial_input(tax_benefit_system) -> None: test_runner.yaml.safe_load(input_yaml), ) - tools.assert_near(simulation.get_array("salary", "2016-10"), [12000, 20000]) + test.assert_near(simulation.get_array("salary", "2016-10"), [12000, 20000]) simulation.calculate("income_tax", "2016-10") simulation.calculate("total_taxes", "2016-10") diff --git a/tests/core/variables/test_variables.py b/tests/core/variables/test_variables.py index d5d85a70d9..d3752df1cc 100644 --- a/tests/core/variables/test_variables.py +++ b/tests/core/variables/test_variables.py @@ -8,8 +8,8 @@ from openfisca_core.periods import DateUnit from openfisca_core.simulation_builder import SimulationBuilder -from openfisca_core.tools import assert_near from openfisca_core.variables import Variable +from openfisca_test import assert_near # Check which date is applied whether it comes from Variable attribute (end) # or formula(s) dates. From d843e2c4f375d77ec126f395dd5b5228edfa7186 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Tue, 24 Sep 2024 11:12:23 +0200 Subject: [PATCH 2/6] feat(tests): fix corner cases --- openfisca_core/indexed_enums/enum_array.py | 7 +- openfisca_core/indexed_enums/types.py | 4 +- openfisca_core/tools/test_runner.py | 12 +- openfisca_core/variables/config.py | 2 +- openfisca_core/variables/types.py | 4 + openfisca_core/variables/variable.py | 5 +- openfisca_tasks/lint.mk | 15 +- openfisca_test/_assertions.py | 138 +++++++ openfisca_test/_parsers.py | 184 ++++++--- openfisca_test/tests/test_assert_near.py | 445 ++++++++++++++++++++- openfisca_test/tests/test_parse.py | 13 +- openfisca_test/types.py | 2 + setup.cfg | 10 +- 13 files changed, 748 insertions(+), 93 deletions(-) diff --git a/openfisca_core/indexed_enums/enum_array.py b/openfisca_core/indexed_enums/enum_array.py index 4ce38809e0..f8b827b24b 100644 --- a/openfisca_core/indexed_enums/enum_array.py +++ b/openfisca_core/indexed_enums/enum_array.py @@ -20,6 +20,11 @@ class EnumArray(numpy.ndarray): # Subclassing ndarray is a little tricky. # To read more about the two following methods, see: # https://docs.scipy.org/doc/numpy-1.13.0/user/basics.subclassing.html#slightly-more-realistic-example-attribute-added-to-existing-array. + + @property + def dtype(self) -> t.ArrayEnum: + return super().dtype + def __new__( cls, input_array: t.Array[t.ArrayEnum], @@ -85,7 +90,7 @@ def decode(self) -> numpy.object_: list(self.possible_values), ) - def decode_to_str(self) -> numpy.str_: + def decode_to_str(self) -> t.Array[t.ArrayStr]: """Return the array of string identifiers corresponding to self. For instance: diff --git a/openfisca_core/indexed_enums/types.py b/openfisca_core/indexed_enums/types.py index 8c725bf8f0..cbdb638f96 100644 --- a/openfisca_core/indexed_enums/types.py +++ b/openfisca_core/indexed_enums/types.py @@ -1,3 +1,3 @@ -from openfisca_core.types import Array, ArrayBytes, ArrayEnum +from openfisca_core.types import Array, ArrayBytes, ArrayEnum, ArrayStr -__all__ = ["Array", "ArrayBytes", "ArrayEnum"] +__all__ = ["Array", "ArrayBytes", "ArrayEnum", "ArrayStr"] diff --git a/openfisca_core/tools/test_runner.py b/openfisca_core/tools/test_runner.py index ef19508314..358985d5be 100644 --- a/openfisca_core/tools/test_runner.py +++ b/openfisca_core/tools/test_runner.py @@ -36,9 +36,9 @@ class Options(TypedDict, total=False): @dataclasses.dataclass(frozen=True) class ErrorMargin: - __root__: dict[str | Literal["default"], float | None] + __root__: Dict[Union[str, Literal["default"]], float] - def __getitem__(self, key: str) -> float | None: + def __getitem__(self, key: str) -> float: if key in self.__root__: return self.__root__[key] @@ -65,7 +65,7 @@ def build_test(params: dict[str, Any]) -> Test: value = params.get(key) if value is None: - value = {"default": None} + value = {"default": 0} elif isinstance(value, (float, int, str)): value = {"default": float(value)} @@ -328,9 +328,9 @@ def check_variable( return assert_near( actual_value, expected_value, - self.test.absolute_error_margin[variable_name], - f"{variable_name}@{period}: ", - self.test.relative_error_margin[variable_name], + message=f"{variable_name}@{period}: ", + absolute_error_margin=self.test.absolute_error_margin[variable_name], + relative_error_margin=self.test.relative_error_margin[variable_name], ) def should_ignore_variable(self, variable_name: str): diff --git a/openfisca_core/variables/config.py b/openfisca_core/variables/config.py index 41862af59f..b12014024f 100644 --- a/openfisca_core/variables/config.py +++ b/openfisca_core/variables/config.py @@ -27,7 +27,7 @@ "is_period_size_independent": False, }, str: { - "dtype": t.ArrayBytes, + "dtype": t.ArrayStr, "default": "", "json_type": "string", "formatted_value_type": "String", diff --git a/openfisca_core/variables/types.py b/openfisca_core/variables/types.py index 62f4c13913..020aaedd17 100644 --- a/openfisca_core/variables/types.py +++ b/openfisca_core/variables/types.py @@ -5,6 +5,8 @@ ArrayEnum, ArrayFloat, ArrayInt, + ArrayObject, + ArrayStr, ) __any__ = [ @@ -14,4 +16,6 @@ ArrayEnum, ArrayFloat, ArrayInt, + ArrayObject, + ArrayStr, ] diff --git a/openfisca_core/variables/variable.py b/openfisca_core/variables/variable.py index 77411c32bb..01964424aa 100644 --- a/openfisca_core/variables/variable.py +++ b/openfisca_core/variables/variable.py @@ -474,7 +474,10 @@ def check_set_value(self, value): return value def default_array(self, array_size): - array = numpy.empty(array_size, dtype=self.dtype) + if numpy.issubdtype(self.dtype, numpy.datetime64): + array = numpy.empty(array_size, dtype="datetime64[D]") + else: + array = numpy.empty(array_size, dtype=self.dtype) if self.value_type == Enum: array.fill(self.default_value.index) return EnumArray(array, self.possible_values) diff --git a/openfisca_tasks/lint.mk b/openfisca_tasks/lint.mk index 3b7e64dce0..dbaf73bc17 100644 --- a/openfisca_tasks/lint.mk +++ b/openfisca_tasks/lint.mk @@ -20,7 +20,6 @@ check-style: $(shell git ls-files "*.py" "*.pyi") lint-doc: \ lint-doc-commons \ lint-doc-entities \ - lint-doc-types \ ; ## Run linters to check for syntax and style errors in the doc. @@ -32,14 +31,22 @@ lint-doc-%: @## able to integrate documentation improvements progresively. @## @$(call print_help,$(subst $*,%,$@:)) - @flake8 --select=D101,D102,D103,DAR openfisca_core/$* openfisca_test - @pylint openfisca_core/$* openfisca_test + @flake8 \ + --select=D101,D102,D103,DAR \ + openfisca_core/$* \ + openfisca_core/types.py \ + openfisca_test \ + stubs + @pylint openfisca_core/$* \ + openfisca_core/$* \ + openfisca_core/types.py \ + openfisca_test \ + stubs @$(call print_pass,$@:) ## Run static type checkers for type errors. check-types: @$(call print_help,$@:) - @command -v pyright && pyright @mypy \ openfisca_core/commons \ openfisca_core/entities \ diff --git a/openfisca_test/_assertions.py b/openfisca_test/_assertions.py index e69de29bb2..ee0bf8f094 100644 --- a/openfisca_test/_assertions.py +++ b/openfisca_test/_assertions.py @@ -0,0 +1,138 @@ +from __future__ import annotations + +from typing import NoReturn + +import numpy + +from . import types as t +from ._parsers import parse + + +def assert_near( + actual: object, + expected: object, + /, + message: str = "", + *, + absolute_error_margin: float = 0, + relative_error_margin: float = 0, +) -> None | NoReturn: + """Assert that two values are near each other. + + Args: + actual: Value returned by the test. + expected: Value that the test should return to pass. + message: Error message to be displayed if the test fails. + absolute_error_margin: Absolute error margin authorized. + relative_error_margin: Relative error margin authorized. + + Returns: + None + + Raises: + ValueError: If the error margin is negative. + AssertionError: If the two values are not near each other. + NotImplementedError: If the data type is not supported. + + Note: + This function cannot be used to assert near periods. + + Examples: + >>> actual = numpy.array([1.0, 2.0, 3.0]) + >>> expected = numpy.array([1.0, 2.0, 2.9]) + >>> assert_near(actual, expected, absolute_error_margin=0.2) + + >>> expected = numpy.array([1.0, 2.0, 2.95]) + >>> assert_near(actual, expected, absolute_error_margin=0.1) + + >>> assert_near(actual, expected, relative_error_margin=0.05) + + >>> assert_near(True, [True]) + + """ + + # Validate absolute_error_margin. + if absolute_error_margin < 0: + raise ValueError("The absolute error margin must be positive.") + + # Validate relative_error_margin. + if relative_error_margin < 0: + raise ValueError("The relative error margin must be positive.") + + # Parse the actual value. + actual = parse(actual) + + # Parse the expected value. + expected = parse(expected) + + # Get the common data type. + try: + common_dtype = numpy.promote_types(actual.dtype, expected.dtype) + + except TypeError: + raise AssertionError( + f"Incompatible types: {actual.dtype} and {expected.dtype}." + ) + + if numpy.issubdtype(common_dtype, numpy.datetime64): + actual = actual.astype(t.ArrayDate) + expected = expected.astype(t.ArrayDate) + assert (actual == expected).all(), f"{message}{actual} differs from {expected}." + return None + + if numpy.issubdtype(common_dtype, numpy.bool_): + actual = actual.astype(t.ArrayBool) + expected = expected.astype(t.ArrayBool) + assert (actual == expected).all(), f"{message}{actual} differs from {expected}." + return None + + if numpy.issubdtype(common_dtype, numpy.bytes_): + actual = actual.astype(t.ArrayBytes) + expected = expected.astype(t.ArrayBytes) + assert (actual == expected).all(), f"{message}{actual} differs from {expected}." + return None + + if numpy.issubdtype(common_dtype, numpy.str_): + actual = actual.astype(t.ArrayStr) + expected = expected.astype(t.ArrayStr) + assert (actual == expected).all(), f"{message}{actual} differs from {expected}." + return None + + if numpy.issubdtype(common_dtype, numpy.int32) or numpy.issubdtype( + common_dtype, numpy.int64 + ): + actual = actual.astype(t.ArrayInt) + expected = expected.astype(t.ArrayInt) + diff = abs(expected - actual) + assert ( + diff == 0 + ).all(), f"{message}{actual} differs from {expected} by {diff}." + return None + + if numpy.issubdtype(common_dtype, numpy.float32) or numpy.issubdtype( + common_dtype, numpy.float64 + ): + actual = actual.astype(t.ArrayFloat) + expected = expected.astype(t.ArrayFloat) + diff = abs(expected - actual) + if absolute_error_margin > 0: + assert (diff <= absolute_error_margin).all(), ( + f"{message}{actual} differs from {expected} with an absolute margin " + f"{diff} > {absolute_error_margin}" + ) + return None + if relative_error_margin > 0: + assert (diff <= abs(relative_error_margin * expected)).all(), ( + f"{message}{actual} differs from {expected} with a relative margin " + f"{diff} > {abs(relative_error_margin * expected)}" + ) + return None + assert ( + actual == expected + ).all(), f"{message}{actual} differs from {expected} by {diff}." + return None + + raise NotImplementedError + + +__all__ = ["assert_near"] diff --git a/openfisca_test/_parsers.py b/openfisca_test/_parsers.py index db12702fa0..8bb58d2308 100644 --- a/openfisca_test/_parsers.py +++ b/openfisca_test/_parsers.py @@ -20,30 +20,39 @@ t.Array[t.ArrayInt], t.Array[t.ArrayFloat], t.Array[t.ArrayBytes], + t.Array[t.ArrayStr], ] -class _ListEnum(list[enum.Enum]): +class _ListEnumArray(tuple[enum.EnumArray, ...]): ... -class _ListDate(list[datetime.date]): +class _ListEnum(tuple[enum.Enum, ...]): ... -class _ListBool(list[bool]): +class _ListDate(tuple[datetime.date, ...]): ... -class _ListInt(list[int]): +class _ListBool(tuple[bool, ...]): ... -class _ListFloat(list[float]): +class _ListInt(tuple[int, ...]): ... -class _ListStr(list[str]): +class _ListFloat(tuple[float, ...]): + ... + + +class _ListBytes(tuple[bytes, ...]): + ... + + +class _ListStr(tuple[str, ...]): ... @@ -57,6 +66,9 @@ def parse(__: object) -> ReturnType: Returns: Parsed value. + Raises: + NotImplementedError: If the value is not supported. + Examples: >>> from openfisca_core import indexed_enums as enum, periods @@ -68,11 +80,14 @@ def parse(__: object) -> ReturnType: >>> instant = period.start >>> date = instant.date + >>> parse(parse(TestEnum)) + array(['tenant', 'owner'], dtype='>> parse(TestEnum) - EnumArray([ ]) + array(['tenant', 'owner'], dtype='>> parse(TestEnum.tenant) - EnumArray([]) + array(['tenant'], dtype='>> parse(period) Traceback (most recent call last): @@ -93,7 +108,7 @@ def parse(__: object) -> ReturnType: >>> parse(1.0) array([1.], dtype=float32) - >>> parse("TestEnum") + >>> parse(b"TestEnum") array([b'TestEnum'], dtype='|S8') >>> parse("2024-01-01") @@ -110,7 +125,7 @@ def parse(__: object) -> ReturnType: array([1.], dtype=float32) >>> parse("parent") - array([b'parent'], dtype='|S6') + array(['parent'], dtype=' ReturnType: return __.astype(t.ArrayBool) if numpy.issubdtype(__.dtype, numpy.int_): return __.astype(t.ArrayInt) - if numpy.issubdtype(__.dtype, numpy.float_): + if numpy.issubdtype(__.dtype, numpy.int32): return __.astype(t.ArrayInt) - for el in __: - if isinstance(el, numpy.ndarray): - return parse(el) + if numpy.issubdtype(__.dtype, numpy.int64): + return __.astype(t.ArrayInt) + if numpy.issubdtype(__.dtype, numpy.float_): + return __.astype(t.ArrayFloat) + if numpy.issubdtype(__.dtype, numpy.float32): + return __.astype(t.ArrayFloat) + if numpy.issubdtype(__.dtype, numpy.float64): + return __.astype(t.ArrayFloat) + if numpy.issubdtype(__.dtype, numpy.datetime64): + return __.astype(t.ArrayDate) if isinstance(__, (list, tuple, numpy.ndarray)): + if isinstance(__[0], enum.EnumArray): + return parse(_ListEnumArray(el for el in __)) if isinstance(__[0], enum.Enum.__class__): return parse(_ListEnum(el for el in __[0])) if isinstance(__[0], enum.Enum): @@ -139,65 +163,126 @@ def parse(__: object) -> ReturnType: return parse(_ListDate(__)) if isinstance(__[0], bool): return parse(_ListBool(__)) - if isinstance(__[0], int): + if all(isinstance(el, int) for el in __): return parse(_ListInt(__)) - if isinstance(__[0], float): + if any(isinstance(el, float) for el in __): return parse(_ListFloat(__)) + if all(isinstance(el, bytes) for el in __): + return parse(_ListBytes(__)) if all(isinstance(el, str) for el in __): return parse(_ListStr(__)) + for el in __: + if isinstance(el, numpy.ndarray): + return parse(el) raise NotImplementedError @parse.register -def _(__: enum.Enum.__class__) -> ReturnType: - return parse(_ListEnum([el for el in __])) +def _(__: enum.EnumArray) -> t.Array[t.ArrayStr]: + return __.decode_to_str() + + +@parse.register +def _(__: enum.Enum.__class__) -> t.Array[t.ArrayStr]: + return numpy.array(tuple(el.name for el in __), dtype=t.ArrayStr) + + +@parse.register +def _(__: enum.Enum) -> t.Array[t.ArrayStr]: + return numpy.array((__.name,), dtype=t.ArrayStr) @parse.register -def _(__: enum.Enum) -> ReturnType: - return parse(_ListEnum([__])) +def _(__: periods.Instant) -> t.Array[t.ArrayDate]: + return numpy.array((__.date,), dtype=t.ArrayDate) @parse.register -def _(__: periods.Instant) -> ReturnType: - return parse(_ListDate([__.date])) +def _(__: datetime.date) -> t.Array[t.ArrayDate]: + return numpy.array((__,), dtype=t.ArrayDate) @parse.register -def _(__: datetime.date) -> ReturnType: - return parse(_ListDate([__])) +def _(__: pendulum.Date) -> t.Array[t.ArrayDate]: + return numpy.array((__,), dtype=t.ArrayDate) @parse.register -def _(__: pendulum.Date) -> ReturnType: - return parse(_ListDate([__])) +def _(__: bool) -> t.Array[t.ArrayBool]: + return numpy.array((__,), dtype=t.ArrayBool) @parse.register -def _(__: bool) -> ReturnType: - return parse(_ListBool([__])) +def _(__: int) -> t.Array[t.ArrayInt]: + return numpy.array((__,), dtype=t.ArrayInt) @parse.register -def _(__: int) -> ReturnType: - return parse(_ListInt([__])) +def _(__: float) -> t.Array[t.ArrayFloat]: + return numpy.array((__,), dtype=t.ArrayFloat) @parse.register -def _(__: float) -> ReturnType: - return parse(_ListFloat([__])) +def _(__: bytes) -> t.Array[t.ArrayBytes]: + return numpy.array((__,), dtype=t.ArrayBytes) @parse.register def _(__: str) -> ReturnType: - return parse(_ListStr([__])) + scalar = tools.eval_expression(__) + if isinstance(scalar, str): + return numpy.array((scalar,), dtype=t.ArrayStr) + return parse(scalar.item()) @parse.register -def _(__: _ListEnum) -> enum.EnumArray: - index = [el.index for el in __] - array = numpy.array(index, dtype=t.ArrayEnum) - return enum.EnumArray(array, __[0].__class__) +def _(__: numpy.bool_) -> t.Array[t.ArrayBool]: + return numpy.array((__,), dtype=t.ArrayBool) + + +@parse.register +def _(__: numpy.int_) -> t.Array[t.ArrayInt]: + return numpy.array((__,), dtype=t.ArrayInt) + + +@parse.register +def _(__: numpy.int32) -> t.Array[t.ArrayInt]: + return numpy.array((__,), dtype=t.ArrayInt) + + +@parse.register +def _(__: numpy.int64) -> t.Array[t.ArrayInt]: + return numpy.array((__,), dtype=t.ArrayInt) + + +@parse.register +def _(__: numpy.float_) -> t.Array[t.ArrayFloat]: + return numpy.array((__,), dtype=t.ArrayFloat) + + +@parse.register +def _(__: numpy.float32) -> t.Array[t.ArrayFloat]: + return numpy.array((__,), dtype=t.ArrayFloat) + + +@parse.register +def _(__: numpy.float64) -> t.Array[t.ArrayFloat]: + return numpy.array((__,), dtype=t.ArrayFloat) + + +@parse.register +def _(__: numpy.datetime64) -> t.Array[t.ArrayDate]: + return numpy.array((__,), dtype=t.ArrayDate) + + +@parse.register +def _(__: _ListEnumArray) -> ReturnType: + return numpy.array([parse(el) for el in __], dtype=t.ArrayStr) + + +@parse.register +def _(__: _ListEnum) -> t.Array[t.ArrayStr]: + return numpy.array(tuple(el.name for el in __), dtype=t.ArrayStr) @parse.register @@ -221,25 +306,18 @@ def _(__: _ListFloat) -> t.Array[t.ArrayFloat]: @parse.register -def _( - __: _ListStr, -) -> Union[ - t.Array[t.ArrayBool], - t.Array[t.ArrayInt], - t.Array[t.ArrayFloat], - t.Array[t.ArrayBytes], -]: +def _(__: _ListBytes) -> t.Array[t.ArrayBytes]: + return numpy.array(__, dtype=t.ArrayBytes) + + +@parse.register +def _(__: _ListStr) -> ReturnType: if len(__) == 1: scalar = tools.eval_expression(__[0]) if isinstance(scalar, str): - return numpy.array([scalar], dtype=t.ArrayBytes) - value = scalar.item() - if isinstance(value, bool): - return numpy.array([value], dtype=t.ArrayBool) - if isinstance(value, int): - return numpy.array([value], dtype=t.ArrayInt) - return numpy.array([value], dtype=t.ArrayFloat) - return numpy.array(__, dtype=t.ArrayBytes) + return numpy.array((scalar,), dtype=t.ArrayStr) + return parse(scalar.item()) + return numpy.array(__, dtype=t.ArrayStr) __all__ = ["parse"] diff --git a/openfisca_test/tests/test_assert_near.py b/openfisca_test/tests/test_assert_near.py index c351be0f9c..fd67aea88f 100644 --- a/openfisca_test/tests/test_assert_near.py +++ b/openfisca_test/tests/test_assert_near.py @@ -1,25 +1,436 @@ +import functools + import numpy +import pytest + +import openfisca_test as test +from openfisca_core import indexed_enums as enum +from openfisca_core import periods + +instant = periods.Instant((2024, 1, 1)) +date = numpy.array(("2024-01-01",), dtype="datetime64[D]") + + +def value(value): + return value + + +def sequence(value): + return (value,) + + +def scalar(value): + if isinstance(value, enum.EnumArray): + return numpy.array(value.decode()) + return numpy.array(value) + -from openfisca_core.tools import assert_near +def array(value): + if isinstance(value, enum.EnumArray): + return numpy.array((value.decode(),)) + return numpy.array((value,)) -def test_date() -> None: - assert_near(numpy.array("2012-03-24", dtype="datetime64[D]"), "2012-03-24") +class TestEnum(enum.Enum): + tenant = "tenant" + owner = "owner" -def test_enum(tax_benefit_system) -> None: - possible_values = tax_benefit_system.variables[ - "housing_occupancy_status" - ].possible_values - value = possible_values.encode(numpy.array(["tenant"])) - expected_value = "tenant" - assert_near(value, expected_value) +@functools.lru_cache +def enum_array(value): + return TestEnum.encode(numpy.array(value)) -def test_enum_2(tax_benefit_system) -> None: - possible_values = tax_benefit_system.variables[ - "housing_occupancy_status" - ].possible_values - value = possible_values.encode(numpy.array(["tenant", "owner"])) - expected_value = ["tenant", "owner"] - assert_near(value, expected_value) +@pytest.mark.parametrize("f", (value, sequence, scalar, array)) +@pytest.mark.parametrize("g", (value, sequence, scalar, array)) +@pytest.mark.parametrize( + "arg1, arg2, expected", + [ + (enum_array(("tenant", "owner")), enum_array(("tenant", "owner")), True), + (enum_array(("tenant", "owner")), enum_array(("tenant",)), False), + (enum_array(("tenant", "owner")), enum_array(("owner",)), False), + (enum_array(("tenant", "owner")), TestEnum, True), + (enum_array(("tenant", "owner")), TestEnum.tenant, False), + (enum_array(("tenant", "owner")), TestEnum.owner, False), + (enum_array(("tenant", "owner")), instant.date, False), + (enum_array(("tenant", "owner")), date, False), + (enum_array(("tenant", "owner")), True, False), + (enum_array(("tenant", "owner")), 0, False), + (enum_array(("tenant", "owner")), 1, False), + (enum_array(("tenant", "owner")), 0.0, False), + (enum_array(("tenant", "owner")), 1.0, False), + (enum_array(("tenant", "owner")), "TestEnum", False), + (enum_array(("tenant", "owner")), "True", False), + (enum_array(("tenant", "owner")), "1", False), + (enum_array(("tenant", "owner")), "1.0", False), + (enum_array(("tenant", "owner")), "tenant", False), + (enum_array(("tenant", "owner")), "owner", False), + (enum_array(("tenant",)), enum_array(("tenant", "owner")), False), + (enum_array(("tenant",)), enum_array(("tenant",)), True), + (enum_array(("tenant",)), enum_array(("owner",)), False), + (enum_array(("tenant",)), TestEnum, False), + (enum_array(("tenant",)), TestEnum.tenant, True), + (enum_array(("tenant",)), TestEnum.owner, False), + (enum_array(("tenant",)), instant.date, False), + (enum_array(("tenant",)), date, False), + (enum_array(("tenant",)), True, False), + (enum_array(("tenant",)), 0, False), + (enum_array(("tenant",)), 1, False), + (enum_array(("tenant",)), 0.0, False), + (enum_array(("tenant",)), 1.0, False), + (enum_array(("tenant",)), "TestEnum", False), + (enum_array(("tenant",)), "True", False), + (enum_array(("tenant",)), "1", False), + (enum_array(("tenant",)), "1.0", False), + (enum_array(("tenant",)), "tenant", True), + (enum_array(("tenant",)), "owner", False), + (enum_array(("owner",)), enum_array(("tenant", "owner")), False), + (enum_array(("owner",)), enum_array(("tenant",)), False), + (enum_array(("owner",)), enum_array(("owner",)), True), + (enum_array(("owner",)), TestEnum, False), + (enum_array(("owner",)), TestEnum.tenant, False), + (enum_array(("owner",)), TestEnum.owner, True), + (enum_array(("owner",)), instant.date, False), + (enum_array(("owner",)), date, False), + (enum_array(("owner",)), True, False), + (enum_array(("owner",)), 0, False), + (enum_array(("owner",)), 1, False), + (enum_array(("owner",)), 0.0, False), + (enum_array(("owner",)), 1.0, False), + (enum_array(("owner",)), "TestEnum", False), + (enum_array(("owner",)), "True", False), + (enum_array(("owner",)), "1", False), + (enum_array(("owner",)), "1.0", False), + (enum_array(("owner",)), "tenant", False), + (enum_array(("owner",)), "owner", True), + (TestEnum, enum_array(("tenant", "owner")), True), + (TestEnum, enum_array(("tenant",)), False), + (TestEnum, enum_array(("owner",)), False), + (TestEnum, TestEnum, True), + (TestEnum, TestEnum.tenant, False), + (TestEnum, TestEnum.owner, False), + (TestEnum, instant.date, False), + (TestEnum, date, False), + (TestEnum, True, False), + (TestEnum, 0, False), + (TestEnum, 1, False), + (TestEnum, 0.0, False), + (TestEnum, 1.0, False), + (TestEnum, "TestEnum", False), + (TestEnum, "True", False), + (TestEnum, "1", False), + (TestEnum, "1.0", False), + (TestEnum, "tenant", False), + (TestEnum, "owner", False), + (TestEnum.tenant, enum_array(("tenant", "owner")), False), + (TestEnum.tenant, enum_array(("tenant",)), True), + (TestEnum.tenant, enum_array(("owner",)), False), + (TestEnum.tenant, TestEnum, False), + (TestEnum.tenant, TestEnum.tenant, True), + (TestEnum.tenant, TestEnum.owner, False), + (TestEnum.tenant, instant.date, False), + (TestEnum.tenant, date, False), + (TestEnum.tenant, True, False), + (TestEnum.tenant, 0, False), + (TestEnum.tenant, 1, False), + (TestEnum.tenant, 0.0, False), + (TestEnum.tenant, 1.0, False), + (TestEnum.tenant, "TestEnum", False), + (TestEnum.tenant, "True", False), + (TestEnum.tenant, "1", False), + (TestEnum.tenant, "1.0", False), + (TestEnum.tenant, "tenant", True), + (TestEnum.tenant, "owner", False), + (TestEnum.owner, enum_array(("tenant", "owner")), False), + (TestEnum.owner, enum_array(("tenant",)), False), + (TestEnum.owner, enum_array(("owner",)), True), + (TestEnum.owner, TestEnum, False), + (TestEnum.owner, TestEnum.tenant, False), + (TestEnum.owner, TestEnum.owner, True), + (TestEnum.owner, instant.date, False), + (TestEnum.owner, date, False), + (TestEnum.owner, True, False), + (TestEnum.owner, 0, False), + (TestEnum.owner, 1, False), + (TestEnum.owner, 0.0, False), + (TestEnum.owner, 1.0, False), + (TestEnum.owner, "TestEnum", False), + (TestEnum.owner, "True", False), + (TestEnum.owner, "1", False), + (TestEnum.owner, "1.0", False), + (TestEnum.owner, "tenant", False), + (TestEnum.owner, "owner", True), + (instant.date, enum_array(("tenant", "owner")), False), + (instant.date, enum_array(("tenant",)), False), + (instant.date, enum_array(("owner",)), False), + (instant.date, TestEnum, False), + (instant.date, TestEnum.tenant, False), + (instant.date, TestEnum.owner, False), + (instant.date, instant.date, True), + (instant.date, date, True), + (instant.date, True, False), + (instant.date, 0, False), + (instant.date, 1, False), + (instant.date, 0.0, False), + (instant.date, 1.0, False), + (instant.date, "TestEnum", False), + (instant.date, "True", False), + (instant.date, "1", False), + (instant.date, "1.0", False), + (instant.date, "tenant", False), + (instant.date, "owner", False), + (date, enum_array(("tenant", "owner")), False), + (date, enum_array(("tenant",)), False), + (date, enum_array(("owner",)), False), + (date, TestEnum, False), + (date, TestEnum.tenant, False), + (date, TestEnum.owner, False), + (date, instant.date, True), + (date, date, True), + (date, True, False), + (date, 0, False), + (date, 1, False), + (date, 0.0, False), + (date, 1.0, False), + (date, "TestEnum", False), + (date, "True", False), + (date, "1", False), + (date, "1.0", False), + (date, "tenant", False), + (date, "owner", False), + (True, enum_array(("tenant", "owner")), False), + (True, enum_array(("tenant",)), False), + (True, enum_array(("owner",)), False), + (True, TestEnum, False), + (True, TestEnum.tenant, False), + (True, TestEnum.owner, False), + (True, instant.date, False), + (True, date, False), + (True, True, True), + (True, 0, False), + (True, 1, True), + (True, 0.0, False), + (True, 1.0, True), + (True, "TestEnum", False), + (True, "True", True), + (True, "1", True), + (True, "1.0", True), + (True, "tenant", False), + (True, "owner", False), + (0, enum_array(("tenant", "owner")), False), + (0, enum_array(("tenant",)), False), + (0, enum_array(("owner",)), False), + (0, TestEnum, False), + (0, TestEnum.tenant, False), + (0, TestEnum.owner, False), + (0, instant.date, False), + (0, date, False), + (0, True, False), + (0, 0, True), + (0, 1, False), + (0, 0.0, True), + (0, 1.0, False), + (0, "TestEnum", False), + (0, "True", False), + (0, "1", False), + (0, "1.0", False), + (0, "tenant", False), + (0, "owner", False), + (1, enum_array(("tenant", "owner")), False), + (1, enum_array(("tenant",)), False), + (1, enum_array(("owner",)), False), + (1, TestEnum, False), + (1, TestEnum.tenant, False), + (1, TestEnum.owner, False), + (1, instant.date, False), + (1, date, False), + (1, True, True), + (1, 0, False), + (1, 1, True), + (1, 0.0, False), + (1, 1.0, True), + (1, "TestEnum", False), + (1, "True", True), + (1, "1", True), + (1, "1.0", True), + (1, "tenant", False), + (1, "owner", False), + (0.0, enum_array(("tenant", "owner")), False), + (0.0, enum_array(("tenant",)), False), + (0.0, enum_array(("owner",)), False), + (0.0, TestEnum, False), + (0.0, TestEnum.tenant, False), + (0.0, TestEnum.owner, False), + (0.0, instant.date, False), + (0.0, date, False), + (0.0, True, False), + (0.0, 0, True), + (0.0, 1, False), + (0.0, 0.0, True), + (0.0, 1.0, False), + (0.0, "TestEnum", False), + (0.0, "True", False), + (0.0, "1", False), + (0.0, "1.0", False), + (0.0, "tenant", False), + (0.0, "owner", False), + (1.0, enum_array(("tenant", "owner")), False), + (1.0, enum_array(("tenant",)), False), + (1.0, enum_array(("owner",)), False), + (1.0, TestEnum, False), + (1.0, TestEnum.tenant, False), + (1.0, TestEnum.owner, False), + (1.0, instant.date, False), + (1.0, date, False), + (1.0, True, True), + (1.0, 0, False), + (1.0, 1, True), + (1.0, 0.0, False), + (1.0, 1.0, True), + (1.0, "TestEnum", False), + (1.0, "True", True), + (1.0, "1", True), + (1.0, "1.0", True), + (1.0, "tenant", False), + (1.0, "owner", False), + (0, enum_array(("tenant", "owner")), False), + (0, enum_array(("tenant",)), False), + (0, enum_array(("owner",)), False), + (0, TestEnum, False), + (0, TestEnum.tenant, False), + (0, TestEnum.owner, False), + (0, instant.date, False), + (0, date, False), + (0, True, False), + (0, 0, True), + (0, 1, False), + (0, 0.0, True), + (0, 1.0, False), + (0, "TestEnum", False), + (0, "True", False), + (0, "1", False), + (0, "1.0", False), + (0, "tenant", False), + (0, "owner", False), + ("TestEnum", enum_array(("tenant", "owner")), False), + ("TestEnum", enum_array(("tenant",)), False), + ("TestEnum", enum_array(("owner",)), False), + ("TestEnum", TestEnum, False), + ("TestEnum", TestEnum.tenant, False), + ("TestEnum", TestEnum.owner, False), + ("TestEnum", instant.date, False), + ("TestEnum", date, False), + ("TestEnum", True, False), + ("TestEnum", 0, False), + ("TestEnum", 1, False), + ("TestEnum", 0.0, False), + ("TestEnum", 1.0, False), + ("TestEnum", "TestEnum", True), + ("TestEnum", "True", False), + ("TestEnum", "1", False), + ("TestEnum", "1.0", False), + ("TestEnum", "tenant", False), + ("TestEnum", "owner", False), + ("True", enum_array(("tenant", "owner")), False), + ("True", enum_array(("tenant",)), False), + ("True", enum_array(("owner",)), False), + ("True", TestEnum, False), + ("True", TestEnum.tenant, False), + ("True", TestEnum.owner, False), + ("True", instant.date, False), + ("True", date, False), + ("True", True, True), + ("True", 0, False), + ("True", 1, True), + ("True", 0.0, False), + ("True", 1.0, True), + ("True", "TestEnum", False), + ("True", "True", True), + ("True", "1", True), + ("True", "1.0", True), + ("True", "tenant", False), + ("True", "owner", False), + ("1", enum_array(("tenant", "owner")), False), + ("1", enum_array(("tenant",)), False), + ("1", enum_array(("owner",)), False), + ("1", TestEnum, False), + ("1", TestEnum.tenant, False), + ("1", TestEnum.owner, False), + ("1", instant.date, False), + ("1", date, False), + ("1", True, True), + ("1", 0, False), + ("1", 1, True), + ("1", 0.0, False), + ("1", 1.0, True), + ("1", "TestEnum", False), + ("1", "True", True), + ("1", "1", True), + ("1", "1.0", True), + ("1", "tenant", False), + ("1", "owner", False), + ("1.0", enum_array(("tenant", "owner")), False), + ("1.0", enum_array(("tenant",)), False), + ("1.0", enum_array(("owner",)), False), + ("1.0", TestEnum, False), + ("1.0", TestEnum.tenant, False), + ("1.0", TestEnum.owner, False), + ("1.0", instant.date, False), + ("1.0", date, False), + ("1.0", True, True), + ("1.0", 0, False), + ("1.0", 1, True), + ("1.0", 0.0, False), + ("1.0", 1.0, True), + ("1.0", "TestEnum", False), + ("1.0", "True", True), + ("1.0", "1", True), + ("1.0", "1.0", True), + ("1.0", "tenant", False), + ("1.0", "owner", False), + ("tenant", enum_array(("tenant", "owner")), False), + ("tenant", enum_array(("tenant",)), True), + ("tenant", enum_array(("owner",)), False), + ("tenant", TestEnum, False), + ("tenant", TestEnum.tenant, True), + ("tenant", TestEnum.owner, False), + ("tenant", instant.date, False), + ("tenant", date, False), + ("tenant", True, False), + ("tenant", 0, False), + ("tenant", 1, False), + ("tenant", 0.0, False), + ("tenant", 1.0, False), + ("tenant", "TestEnum", False), + ("tenant", "True", False), + ("tenant", "1", False), + ("tenant", "1.0", False), + ("tenant", "tenant", True), + ("tenant", "owner", False), + ("owner", enum_array(("tenant", "owner")), False), + ("owner", enum_array(("tenant",)), False), + ("owner", enum_array(("owner",)), True), + ("owner", TestEnum, False), + ("owner", TestEnum.tenant, False), + ("owner", TestEnum.owner, True), + ("owner", instant.date, False), + ("owner", date, False), + ("owner", True, False), + ("owner", 0, False), + ("owner", 1, False), + ("owner", 0.0, False), + ("owner", 1.0, False), + ("owner", "TestEnum", False), + ("owner", "True", False), + ("owner", "1", False), + ("owner", "1.0", False), + ("owner", "tenant", False), + ("owner", "owner", True), + ], +) +def test_assert_near(f, g, arg1, arg2, expected): + if expected: + test.assert_near(f(arg1), g(arg2)) + else: + with pytest.raises(AssertionError): + test.assert_near(f(arg1), g(arg2)) diff --git a/openfisca_test/tests/test_parse.py b/openfisca_test/tests/test_parse.py index af74c2ed70..38314a9d31 100644 --- a/openfisca_test/tests/test_parse.py +++ b/openfisca_test/tests/test_parse.py @@ -24,10 +24,14 @@ def sequence(value): def scalar(value): + if isinstance(value, enum.EnumArray): + return numpy.array(value.decode()) return numpy.array(value) def array(value): + if isinstance(value, enum.EnumArray): + return numpy.array([value.decode()]) return numpy.array([value]) @@ -39,18 +43,19 @@ def enum_array(value): @pytest.mark.parametrize( "arg, expected", [ - (TestEnum, enum_array(["tenant", "owner"])), - (TestEnum.tenant, enum_array(["tenant"])), + (enum_array(["tenant", "owner"]), numpy.array(["tenant", "owner"])), + (TestEnum, numpy.array(["tenant", "owner"], dtype=" Date: Tue, 24 Sep 2024 11:19:50 +0200 Subject: [PATCH 3/6] chore(version): bump --- CHANGELOG.md | 14 ++++++++++++++ setup.py | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 002cf14714..df9c9e2d88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,24 @@ # Changelog +<<<<<<< HEAD ### 41.5.6 [#1185](https://github.com/openfisca/openfisca-core/pull/1185) #### Technical changes - Remove pre Python 3.9 syntax. +======= +## 41.6.0 [#1228](https://github.com/openfisca/openfisca-core/pull/1228) + +#### New features + +- Introduce `openfisca_test`. + - Provides `assert_near` and `parse` to prepare tests. + +#### Technical changes + +- Move `assert_near` to `openfisca_test`. +- Add tests to `assert_near`. +>>>>>>> 9c701ebfc (chore(version): bump) ### 41.5.5 [#1220](https://github.com/openfisca/openfisca-core/pull/1220) diff --git a/setup.py b/setup.py index 64dae0774b..3cf7b03aff 100644 --- a/setup.py +++ b/setup.py @@ -70,7 +70,7 @@ setup( name="OpenFisca-Core", - version="41.5.6", + version="42.0.0", author="OpenFisca Team", author_email="contact@openfisca.org", classifiers=[ From 9880dc1b9801e037b4ab796b37787bbdc85e0819 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Tue, 24 Sep 2024 21:47:28 +0200 Subject: [PATCH 4/6] refactor(assert_near): simplify --- STYLEGUIDE.md | 3 +- openfisca_core/holders/tests/test_helpers.py | 6 +- openfisca_core/indexed_enums/__init__.py | 9 +- openfisca_core/indexed_enums/enum.py | 1 - openfisca_core/indexed_enums/enum_array.py | 13 +- openfisca_core/periods/instant_.py | 2 +- openfisca_core/tools/test_runner.py | 2 +- openfisca_core/types.py | 33 +--- openfisca_core/variables/__init__.py | 16 +- openfisca_core/variables/config.py | 19 +- openfisca_core/variables/types.py | 21 --- openfisca_tasks/lint.mk | 26 ++- openfisca_test/_assertions.py | 29 ++- openfisca_test/_parsers.py | 165 ++++++++---------- openfisca_test/tests/test_assert_near.py | 3 +- openfisca_test/tests/test_parse.py | 3 +- openfisca_test/types.py | 23 --- setup.cfg | 6 +- stubs/numexpr/__init__.pyi | 3 +- .../test_linear_average_rate_tax_scale.py | 12 +- .../test_marginal_amount_tax_scale.py | 4 +- .../test_marginal_rate_tax_scale.py | 28 +-- .../test_single_amount_tax_scale.py | 4 +- .../tax_scales/test_tax_scales_commons.py | 6 +- tests/core/test_calculate_output.py | 4 +- tests/core/test_countries.py | 12 +- tests/core/test_cycles.py | 12 +- tests/core/test_entities.py | 126 ++++++------- tests/core/test_holders.py | 4 +- tests/core/test_simulation_builder.py | 18 +- 30 files changed, 249 insertions(+), 364 deletions(-) delete mode 100644 openfisca_core/variables/types.py diff --git a/STYLEGUIDE.md b/STYLEGUIDE.md index eb46d446bf..3f409a265b 100644 --- a/STYLEGUIDE.md +++ b/STYLEGUIDE.md @@ -72,8 +72,9 @@ And avoid: # No from openfisca_country_template.entities import Person -from openfisca_core import axes, variables +from openfisca_core import variables from openfisca_test import assert_near +from openfisca_core import axes from numpy import ndarray from copy import deepcopy diff --git a/openfisca_core/holders/tests/test_helpers.py b/openfisca_core/holders/tests/test_helpers.py index c76f01f88e..cce4fa1e06 100644 --- a/openfisca_core/holders/tests/test_helpers.py +++ b/openfisca_core/holders/tests/test_helpers.py @@ -1,6 +1,6 @@ import pytest -import openfisca_test as test +import openfisca_test as tools from openfisca_core import holders from openfisca_core.entities import Entity from openfisca_core.holders import Holder @@ -82,7 +82,7 @@ def test_set_input_dispatch_by_period( holders.set_input_dispatch_by_period(holder, dispatch_period, values) total = sum(map(holder.get_array, holder.get_known_periods())) - test.assert_near(total, expected, absolute_error_margin=0.001) + tools.assert_near(total, expected, absolute_error_margin=0.001) @pytest.mark.parametrize( @@ -132,4 +132,4 @@ def test_set_input_divide_by_period( holders.set_input_divide_by_period(holder, divide_period, values) last = holder.get_array(holder.get_known_periods()[-1]) - test.assert_near(last, expected, absolute_error_margin=0.001) + tools.assert_near(last, expected, absolute_error_margin=0.001) diff --git a/openfisca_core/indexed_enums/__init__.py b/openfisca_core/indexed_enums/__init__.py index 9d63bf39f4..874a7e1f9b 100644 --- a/openfisca_core/indexed_enums/__init__.py +++ b/openfisca_core/indexed_enums/__init__.py @@ -21,9 +21,6 @@ # # See: https://www.python.org/dev/peps/pep-0008/#imports -from . import types -from .config import ENUM_ARRAY_DTYPE -from .enum import Enum -from .enum_array import EnumArray - -__all__ = ["ENUM_ARRAY_DTYPE", "Enum", "EnumArray", "types"] +from .config import ENUM_ARRAY_DTYPE # noqa: F401 +from .enum import Enum # noqa: F401 +from .enum_array import EnumArray # noqa: F401 diff --git a/openfisca_core/indexed_enums/enum.py b/openfisca_core/indexed_enums/enum.py index 4f2c887dac..25b02cee74 100644 --- a/openfisca_core/indexed_enums/enum.py +++ b/openfisca_core/indexed_enums/enum.py @@ -4,7 +4,6 @@ import numpy -from . import types as t from .config import ENUM_ARRAY_DTYPE from .enum_array import EnumArray diff --git a/openfisca_core/indexed_enums/enum_array.py b/openfisca_core/indexed_enums/enum_array.py index f8b827b24b..86b55f9f48 100644 --- a/openfisca_core/indexed_enums/enum_array.py +++ b/openfisca_core/indexed_enums/enum_array.py @@ -5,8 +5,6 @@ import numpy -from . import types as t - if typing.TYPE_CHECKING: from openfisca_core.indexed_enums import Enum @@ -20,15 +18,10 @@ class EnumArray(numpy.ndarray): # Subclassing ndarray is a little tricky. # To read more about the two following methods, see: # https://docs.scipy.org/doc/numpy-1.13.0/user/basics.subclassing.html#slightly-more-realistic-example-attribute-added-to-existing-array. - - @property - def dtype(self) -> t.ArrayEnum: - return super().dtype - def __new__( cls, - input_array: t.Array[t.ArrayEnum], - possible_values: Optional[Type[Enum]] = None, + input_array: numpy.int_, + possible_values: type[Enum] | None = None, ) -> EnumArray: obj = numpy.asarray(input_array).view(cls) obj.possible_values = possible_values @@ -90,7 +83,7 @@ def decode(self) -> numpy.object_: list(self.possible_values), ) - def decode_to_str(self) -> t.Array[t.ArrayStr]: + def decode_to_str(self) -> numpy.str_: """Return the array of string identifiers corresponding to self. For instance: diff --git a/openfisca_core/periods/instant_.py b/openfisca_core/periods/instant_.py index 757c543222..5042209492 100644 --- a/openfisca_core/periods/instant_.py +++ b/openfisca_core/periods/instant_.py @@ -90,7 +90,7 @@ def __str__(self) -> str: return instant_str @property - def date(self) -> pendulum.Date: + def date(self): instant_date = config.date_by_instant_cache.get(self) if instant_date is None: diff --git a/openfisca_core/tools/test_runner.py b/openfisca_core/tools/test_runner.py index 358985d5be..9a27f0047e 100644 --- a/openfisca_core/tools/test_runner.py +++ b/openfisca_core/tools/test_runner.py @@ -36,7 +36,7 @@ class Options(TypedDict, total=False): @dataclasses.dataclass(frozen=True) class ErrorMargin: - __root__: Dict[Union[str, Literal["default"]], float] + __root__: dict[str | Literal["default"], float] def __getitem__(self, key: str) -> float: if key in self.__root__: diff --git a/openfisca_core/types.py b/openfisca_core/types.py index 3717dfc8b3..16a1f0e90e 100644 --- a/openfisca_core/types.py +++ b/openfisca_core/types.py @@ -10,42 +10,11 @@ import numpy -# Type aliases - -#: Type for arrays of any type. -ArrayAny: TypeAlias = numpy.generic - -#: Type for arrays of booleans. -ArrayBool: TypeAlias = numpy.bool_ - -#: Type for arrays of bytes. -ArrayBytes: TypeAlias = numpy.bytes_ - -#: Type for arrays of dates. -ArrayDate: TypeAlias = numpy.datetime64 - -#: Type for arrays of enums. -ArrayEnum: TypeAlias = numpy.int16 - -#: Type for arrays of floats. -ArrayFloat: TypeAlias = numpy.float32 - -#: Type for arrays of integers. -ArrayInt: TypeAlias = numpy.int32 - -#: Type for arrays of Python objects. -ArrayObject: TypeAlias = numpy.object_ - -#: Type for arrays of strings. -ArrayStr: TypeAlias = numpy.str_ - -#: Generic numpy type an array can have. N = TypeVar("N", bound=numpy.generic, covariant=True) -#: Type representing a numpy array. +#: Type representing an numpy array. Array: TypeAlias = NDArray[N] -#: Generic type a sequence can have. L = TypeVar("L") #: Type representing an array-like object. diff --git a/openfisca_core/variables/__init__.py b/openfisca_core/variables/__init__.py index e4ec339ca8..1ab191c5ce 100644 --- a/openfisca_core/variables/__init__.py +++ b/openfisca_core/variables/__init__.py @@ -21,16 +21,6 @@ # # See: https://www.python.org/dev/peps/pep-0008/#imports -from . import types -from .config import FORMULA_NAME_PREFIX, VALUE_TYPES -from .helpers import get_annualized_variable, get_neutralized_variable -from .variable import Variable - -__all__ = [ - "FORMULA_NAME_PREFIX", - "VALUE_TYPES", - "Variable", - "get_annualized_variable", - "get_neutralized_variable", - "types", -] +from .config import FORMULA_NAME_PREFIX, VALUE_TYPES # noqa: F401 +from .helpers import get_annualized_variable, get_neutralized_variable # noqa: F401 +from .variable import Variable # noqa: F401 diff --git a/openfisca_core/variables/config.py b/openfisca_core/variables/config.py index b12014024f..54270145bf 100644 --- a/openfisca_core/variables/config.py +++ b/openfisca_core/variables/config.py @@ -1,46 +1,47 @@ import datetime -from openfisca_core import indexed_enums as enum +import numpy -from . import types as t +from openfisca_core import indexed_enums +from openfisca_core.indexed_enums import Enum VALUE_TYPES = { bool: { - "dtype": t.ArrayBool, + "dtype": numpy.bool_, "default": False, "json_type": "boolean", "formatted_value_type": "Boolean", "is_period_size_independent": True, }, int: { - "dtype": t.ArrayInt, + "dtype": numpy.int32, "default": 0, "json_type": "integer", "formatted_value_type": "Int", "is_period_size_independent": False, }, float: { - "dtype": t.ArrayFloat, + "dtype": numpy.float32, "default": 0, "json_type": "number", "formatted_value_type": "Float", "is_period_size_independent": False, }, str: { - "dtype": t.ArrayStr, + "dtype": object, "default": "", "json_type": "string", "formatted_value_type": "String", "is_period_size_independent": True, }, - enum.Enum: { - "dtype": t.ArrayEnum, + Enum: { + "dtype": indexed_enums.ENUM_ARRAY_DTYPE, "json_type": "string", "formatted_value_type": "String", "is_period_size_independent": True, }, datetime.date: { - "dtype": t.ArrayDate, + "dtype": "datetime64[D]", "default": datetime.date.fromtimestamp(0), # 0 == 1970-01-01 "json_type": "string", "formatted_value_type": "Date", diff --git a/openfisca_core/variables/types.py b/openfisca_core/variables/types.py deleted file mode 100644 index 020aaedd17..0000000000 --- a/openfisca_core/variables/types.py +++ /dev/null @@ -1,21 +0,0 @@ -from openfisca_core.types import ( - ArrayBool, - ArrayBytes, - ArrayDate, - ArrayEnum, - ArrayFloat, - ArrayInt, - ArrayObject, - ArrayStr, -) - -__any__ = [ - ArrayBool, - ArrayBytes, - ArrayDate, - ArrayEnum, - ArrayFloat, - ArrayInt, - ArrayObject, - ArrayStr, -] diff --git a/openfisca_tasks/lint.mk b/openfisca_tasks/lint.mk index dbaf73bc17..772142faae 100644 --- a/openfisca_tasks/lint.mk +++ b/openfisca_tasks/lint.mk @@ -1,5 +1,5 @@ ## Lint the codebase. -lint: check-syntax-errors check-style lint-doc +lint: check-syntax-errors check-style check-doc @$(call print_pass,$@:) ## Compile python files to check for syntax errors. @@ -17,28 +17,23 @@ check-style: $(shell git ls-files "*.py" "*.pyi") @$(call print_pass,$@:) ## Run linters to check for syntax and style errors in the doc. -lint-doc: \ - lint-doc-commons \ - lint-doc-entities \ - ; - -## Run linters to check for syntax and style errors in the doc. -lint-doc-%: +check-doc: @## These checks are exclusively related to doc/strings/test. @## @## They can be integrated into setup.cfg once all checks pass. @## The reason they're here is because otherwise we wouldn't be @## able to integrate documentation improvements progresively. @## - @$(call print_help,$(subst $*,%,$@:)) - @flake8 \ - --select=D101,D102,D103,DAR \ - openfisca_core/$* \ + @$(call print_help,$@:) + @flake8 --select=D101,D102,D103,DAR \ + openfisca_core/commons \ + openfisca_core/entities \ openfisca_core/types.py \ openfisca_test \ stubs - @pylint openfisca_core/$* \ - openfisca_core/$* \ + @pylint \ + openfisca_core/commons \ + openfisca_core/entities \ openfisca_core/types.py \ openfisca_test \ stubs @@ -51,7 +46,8 @@ check-types: openfisca_core/commons \ openfisca_core/entities \ openfisca_core/types.py \ - openfisca_test + openfisca_test \ + stubs @$(call print_pass,$@:) ## Run code formatters to correct style errors. diff --git a/openfisca_test/_assertions.py b/openfisca_test/_assertions.py index ee0bf8f094..c2267725a0 100644 --- a/openfisca_test/_assertions.py +++ b/openfisca_test/_assertions.py @@ -1,10 +1,7 @@ from __future__ import annotations -from typing import NoReturn - import numpy -from . import types as t from ._parsers import parse @@ -16,7 +13,7 @@ def assert_near( *, absolute_error_margin: float = 0, relative_error_margin: float = 0, -) -> None | NoReturn: +) -> None: """Assert that two values are near each other. Args: @@ -75,34 +72,34 @@ def assert_near( ) if numpy.issubdtype(common_dtype, numpy.datetime64): - actual = actual.astype(t.ArrayDate) - expected = expected.astype(t.ArrayDate) + actual = actual.astype(numpy.datetime64) + expected = expected.astype(numpy.datetime64) assert (actual == expected).all(), f"{message}{actual} differs from {expected}." return None if numpy.issubdtype(common_dtype, numpy.bool_): - actual = actual.astype(t.ArrayBool) - expected = expected.astype(t.ArrayBool) + actual = actual.astype(numpy.bool_) + expected = expected.astype(numpy.bool_) assert (actual == expected).all(), f"{message}{actual} differs from {expected}." return None if numpy.issubdtype(common_dtype, numpy.bytes_): - actual = actual.astype(t.ArrayBytes) - expected = expected.astype(t.ArrayBytes) + actual = actual.astype(numpy.bytes_) + expected = expected.astype(numpy.bytes_) assert (actual == expected).all(), f"{message}{actual} differs from {expected}." return None if numpy.issubdtype(common_dtype, numpy.str_): - actual = actual.astype(t.ArrayStr) - expected = expected.astype(t.ArrayStr) + actual = actual.astype(numpy.str_) + expected = expected.astype(numpy.str_) assert (actual == expected).all(), f"{message}{actual} differs from {expected}." return None if numpy.issubdtype(common_dtype, numpy.int32) or numpy.issubdtype( common_dtype, numpy.int64 ): - actual = actual.astype(t.ArrayInt) - expected = expected.astype(t.ArrayInt) + actual = actual.astype(numpy.int32) + expected = expected.astype(numpy.int32) diff = abs(expected - actual) assert ( diff == 0 @@ -112,8 +109,8 @@ def assert_near( if numpy.issubdtype(common_dtype, numpy.float32) or numpy.issubdtype( common_dtype, numpy.float64 ): - actual = actual.astype(t.ArrayFloat) - expected = expected.astype(t.ArrayFloat) + actual = actual.astype(numpy.float32) + expected = expected.astype(numpy.float32) diff = abs(expected - actual) if absolute_error_margin > 0: assert (diff <= absolute_error_margin).all(), ( diff --git a/openfisca_test/_parsers.py b/openfisca_test/_parsers.py index 8bb58d2308..fd9e1c5cb1 100644 --- a/openfisca_test/_parsers.py +++ b/openfisca_test/_parsers.py @@ -1,4 +1,5 @@ -from typing import NoReturn, Union +from numpy.typing import NDArray +from typing import Union from typing_extensions import TypeAlias import datetime @@ -7,53 +8,41 @@ import numpy import pendulum -from openfisca_core import indexed_enums as enum -from openfisca_core import periods, tools - -from . import types as t +from openfisca_core import indexed_enums as enum, periods, tools ReturnType: TypeAlias = Union[ - NoReturn, enum.EnumArray, - t.Array[t.ArrayDate], - t.Array[t.ArrayBool], - t.Array[t.ArrayInt], - t.Array[t.ArrayFloat], - t.Array[t.ArrayBytes], - t.Array[t.ArrayStr], + NDArray[numpy.datetime64], + NDArray[numpy.bool_], + NDArray[numpy.int32], + NDArray[numpy.float32], + NDArray[numpy.bytes_], + NDArray[numpy.str_], ] -class _ListEnumArray(tuple[enum.EnumArray, ...]): - ... +class _ListEnumArray(tuple[enum.EnumArray, ...]): ... -class _ListEnum(tuple[enum.Enum, ...]): - ... +class _ListEnum(tuple[enum.Enum, ...]): ... -class _ListDate(tuple[datetime.date, ...]): - ... +class _ListDate(tuple[datetime.date, ...]): ... -class _ListBool(tuple[bool, ...]): - ... +class _ListBool(tuple[bool, ...]): ... -class _ListInt(tuple[int, ...]): - ... +class _ListInt(tuple[int, ...]): ... -class _ListFloat(tuple[float, ...]): - ... +class _ListFloat(tuple[float, ...]): ... -class _ListBytes(tuple[bytes, ...]): - ... +class _ListBytes(tuple[bytes, ...]): ... -class _ListStr(tuple[str, ...]): - ... +class _ListStr(tuple[str, ...]): ... @functools.singledispatch @@ -133,21 +122,21 @@ def parse(__: object) -> ReturnType: return parse(__.item()) if isinstance(__, numpy.ndarray): if numpy.issubdtype(__.dtype, numpy.bool_): - return __.astype(t.ArrayBool) + return __.astype(numpy.bool_) if numpy.issubdtype(__.dtype, numpy.int_): - return __.astype(t.ArrayInt) + return __.astype(numpy.int32) if numpy.issubdtype(__.dtype, numpy.int32): - return __.astype(t.ArrayInt) + return __.astype(numpy.int32) if numpy.issubdtype(__.dtype, numpy.int64): - return __.astype(t.ArrayInt) - if numpy.issubdtype(__.dtype, numpy.float_): - return __.astype(t.ArrayFloat) + return __.astype(numpy.int32) + if numpy.issubdtype(__.dtype, numpy.float64): + return __.astype(numpy.float32) if numpy.issubdtype(__.dtype, numpy.float32): - return __.astype(t.ArrayFloat) + return __.astype(numpy.float32) if numpy.issubdtype(__.dtype, numpy.float64): - return __.astype(t.ArrayFloat) + return __.astype(numpy.float32) if numpy.issubdtype(__.dtype, numpy.datetime64): - return __.astype(t.ArrayDate) + return __.astype(numpy.datetime64) if isinstance(__, (list, tuple, numpy.ndarray)): if isinstance(__[0], enum.EnumArray): return parse(_ListEnumArray(el for el in __)) @@ -178,136 +167,136 @@ def parse(__: object) -> ReturnType: @parse.register -def _(__: enum.EnumArray) -> t.Array[t.ArrayStr]: +def _(__: enum.EnumArray) -> NDArray[numpy.str_]: return __.decode_to_str() @parse.register -def _(__: enum.Enum.__class__) -> t.Array[t.ArrayStr]: - return numpy.array(tuple(el.name for el in __), dtype=t.ArrayStr) +def _(__: enum.Enum.__class__) -> NDArray[numpy.str_]: + return numpy.array(tuple(el.name for el in __), dtype=numpy.str_) @parse.register -def _(__: enum.Enum) -> t.Array[t.ArrayStr]: - return numpy.array((__.name,), dtype=t.ArrayStr) +def _(__: enum.Enum) -> NDArray[numpy.str_]: + return numpy.array((__.name,), dtype=numpy.str_) @parse.register -def _(__: periods.Instant) -> t.Array[t.ArrayDate]: - return numpy.array((__.date,), dtype=t.ArrayDate) +def _(__: periods.Instant) -> NDArray[numpy.datetime64]: + return numpy.array((__.date,), dtype=numpy.datetime64) @parse.register -def _(__: datetime.date) -> t.Array[t.ArrayDate]: - return numpy.array((__,), dtype=t.ArrayDate) +def _(__: datetime.date) -> NDArray[numpy.datetime64]: + return numpy.array((__,), dtype=numpy.datetime64) @parse.register -def _(__: pendulum.Date) -> t.Array[t.ArrayDate]: - return numpy.array((__,), dtype=t.ArrayDate) +def _(__: pendulum.Date) -> NDArray[numpy.datetime64]: + return numpy.array((__,), dtype=numpy.datetime64) @parse.register -def _(__: bool) -> t.Array[t.ArrayBool]: - return numpy.array((__,), dtype=t.ArrayBool) +def _(__: bool) -> NDArray[numpy.bool_]: + return numpy.array((__,), dtype=numpy.bool_) @parse.register -def _(__: int) -> t.Array[t.ArrayInt]: - return numpy.array((__,), dtype=t.ArrayInt) +def _(__: int) -> NDArray[numpy.int32]: + return numpy.array((__,), dtype=numpy.int32) @parse.register -def _(__: float) -> t.Array[t.ArrayFloat]: - return numpy.array((__,), dtype=t.ArrayFloat) +def _(__: float) -> NDArray[numpy.float32]: + return numpy.array((__,), dtype=numpy.float32) @parse.register -def _(__: bytes) -> t.Array[t.ArrayBytes]: - return numpy.array((__,), dtype=t.ArrayBytes) +def _(__: bytes) -> NDArray[numpy.bytes_]: + return numpy.array((__,), dtype=numpy.bytes_) @parse.register def _(__: str) -> ReturnType: scalar = tools.eval_expression(__) if isinstance(scalar, str): - return numpy.array((scalar,), dtype=t.ArrayStr) + return numpy.array((scalar,), dtype=numpy.str_) return parse(scalar.item()) @parse.register -def _(__: numpy.bool_) -> t.Array[t.ArrayBool]: - return numpy.array((__,), dtype=t.ArrayBool) +def _(__: numpy.bool_) -> NDArray[numpy.bool_]: + return numpy.array((__,), dtype=numpy.bool_) @parse.register -def _(__: numpy.int_) -> t.Array[t.ArrayInt]: - return numpy.array((__,), dtype=t.ArrayInt) +def _(__: numpy.int_) -> NDArray[numpy.int32]: + return numpy.array((__,), dtype=numpy.int32) @parse.register -def _(__: numpy.int32) -> t.Array[t.ArrayInt]: - return numpy.array((__,), dtype=t.ArrayInt) +def _(__: numpy.int32) -> NDArray[numpy.int32]: + return numpy.array((__,), dtype=numpy.int32) @parse.register -def _(__: numpy.int64) -> t.Array[t.ArrayInt]: - return numpy.array((__,), dtype=t.ArrayInt) +def _(__: numpy.int64) -> NDArray[numpy.int32]: + return numpy.array((__,), dtype=numpy.int32) @parse.register -def _(__: numpy.float_) -> t.Array[t.ArrayFloat]: - return numpy.array((__,), dtype=t.ArrayFloat) +def _(__: numpy.float64) -> NDArray[numpy.float32]: + return numpy.array((__,), dtype=numpy.float32) @parse.register -def _(__: numpy.float32) -> t.Array[t.ArrayFloat]: - return numpy.array((__,), dtype=t.ArrayFloat) +def _(__: numpy.float32) -> NDArray[numpy.float32]: + return numpy.array((__,), dtype=numpy.float32) @parse.register -def _(__: numpy.float64) -> t.Array[t.ArrayFloat]: - return numpy.array((__,), dtype=t.ArrayFloat) +def _(__: numpy.float64) -> NDArray[numpy.float32]: + return numpy.array((__,), dtype=numpy.float32) @parse.register -def _(__: numpy.datetime64) -> t.Array[t.ArrayDate]: - return numpy.array((__,), dtype=t.ArrayDate) +def _(__: numpy.datetime64) -> NDArray[numpy.datetime64]: + return numpy.array((__,), dtype=numpy.datetime64) @parse.register def _(__: _ListEnumArray) -> ReturnType: - return numpy.array([parse(el) for el in __], dtype=t.ArrayStr) + return numpy.array([parse(el) for el in __], dtype=numpy.str_) @parse.register -def _(__: _ListEnum) -> t.Array[t.ArrayStr]: - return numpy.array(tuple(el.name for el in __), dtype=t.ArrayStr) +def _(__: _ListEnum) -> NDArray[numpy.str_]: + return numpy.array(tuple(el.name for el in __), dtype=numpy.str_) @parse.register -def _(__: _ListDate) -> t.Array[t.ArrayDate]: - return numpy.array(__, dtype=t.ArrayDate) +def _(__: _ListDate) -> NDArray[numpy.datetime64]: + return numpy.array(__, dtype=numpy.datetime64) @parse.register -def _(__: _ListBool) -> t.Array[t.ArrayBool]: - return numpy.array(__, dtype=t.ArrayBool) +def _(__: _ListBool) -> NDArray[numpy.bool_]: + return numpy.array(__, dtype=numpy.bool_) @parse.register -def _(__: _ListInt) -> t.Array[t.ArrayInt]: - return numpy.array(__, dtype=t.ArrayInt) +def _(__: _ListInt) -> NDArray[numpy.int32]: + return numpy.array(__, dtype=numpy.int32) @parse.register -def _(__: _ListFloat) -> t.Array[t.ArrayFloat]: - return numpy.array(__, dtype=t.ArrayFloat) +def _(__: _ListFloat) -> NDArray[numpy.float32]: + return numpy.array(__, dtype=numpy.float32) @parse.register -def _(__: _ListBytes) -> t.Array[t.ArrayBytes]: - return numpy.array(__, dtype=t.ArrayBytes) +def _(__: _ListBytes) -> NDArray[numpy.bytes_]: + return numpy.array(__, dtype=numpy.bytes_) @parse.register @@ -315,9 +304,9 @@ def _(__: _ListStr) -> ReturnType: if len(__) == 1: scalar = tools.eval_expression(__[0]) if isinstance(scalar, str): - return numpy.array((scalar,), dtype=t.ArrayStr) + return numpy.array((scalar,), dtype=numpy.str_) return parse(scalar.item()) - return numpy.array(__, dtype=t.ArrayStr) + return numpy.array(__, dtype=numpy.str_) __all__ = ["parse"] diff --git a/openfisca_test/tests/test_assert_near.py b/openfisca_test/tests/test_assert_near.py index fd67aea88f..eda1781dd4 100644 --- a/openfisca_test/tests/test_assert_near.py +++ b/openfisca_test/tests/test_assert_near.py @@ -4,8 +4,7 @@ import pytest import openfisca_test as test -from openfisca_core import indexed_enums as enum -from openfisca_core import periods +from openfisca_core import indexed_enums as enum, periods instant = periods.Instant((2024, 1, 1)) date = numpy.array(("2024-01-01",), dtype="datetime64[D]") diff --git a/openfisca_test/tests/test_parse.py b/openfisca_test/tests/test_parse.py index 38314a9d31..b10694eb1c 100644 --- a/openfisca_test/tests/test_parse.py +++ b/openfisca_test/tests/test_parse.py @@ -4,8 +4,7 @@ import pytest import openfisca_test as test -from openfisca_core import indexed_enums as enum -from openfisca_core import periods +from openfisca_core import indexed_enums as enum, periods date = numpy.array(["2024-01-01"], dtype="datetime64[D]") diff --git a/openfisca_test/types.py b/openfisca_test/types.py index b7fcb3b551..e69de29bb2 100644 --- a/openfisca_test/types.py +++ b/openfisca_test/types.py @@ -1,23 +0,0 @@ -from openfisca_core.types import ( - Array, - ArrayBool, - ArrayBytes, - ArrayDate, - ArrayEnum, - ArrayFloat, - ArrayInt, - ArrayObject, - ArrayStr, -) - -__all__ = [ - "Array", - "ArrayBool", - "ArrayDate", - "ArrayBytes", - "ArrayEnum", - "ArrayFloat", - "ArrayInt", - "ArrayObject", - "ArrayStr", -] diff --git a/setup.cfg b/setup.cfg index a0d0a5039f..e07849f330 100644 --- a/setup.cfg +++ b/setup.cfg @@ -22,10 +22,10 @@ include-in-doctest = openfisca_test max-line-length = 88 per-file-ignores = - */types.py:D101,D102,E704 + */types.py:D101,D102 */test_*.py:D101,D102,D103 */__init__.py:F401 - */__init__.pyi:E302,E704 + */__init__.pyi:E302 rst-directives = attribute, deprecated, seealso, versionadded, versionchanged rst-roles = any, attr, class, exc, func, meth, mod, obj strictness = short @@ -39,7 +39,7 @@ enable = C0115, C0116, R0401 per-file-ignores = /tests/.*:C0115,C0116 types.py:C0115,C0116 - + /stubs/.*:C0116 score = no [isort] diff --git a/stubs/numexpr/__init__.pyi b/stubs/numexpr/__init__.pyi index eeca098e9d..4537787c24 100644 --- a/stubs/numexpr/__init__.pyi +++ b/stubs/numexpr/__init__.pyi @@ -1,5 +1,4 @@ from numpy.typing import NDArray as Array -from typing import NoReturn from typing_extensions import TypeAlias import numpy @@ -10,4 +9,4 @@ ArrayFloat: TypeAlias = numpy.float32 def evaluate( __ex: str, *__args: object, **__kwargs: object -) -> NoReturn | Array[ArrayBool | ArrayInt | ArrayFloat]: ... +) -> Array[ArrayBool | ArrayInt | ArrayFloat]: ... diff --git a/tests/core/tax_scales/test_linear_average_rate_tax_scale.py b/tests/core/tax_scales/test_linear_average_rate_tax_scale.py index 13cab611d2..1917b3ef37 100644 --- a/tests/core/tax_scales/test_linear_average_rate_tax_scale.py +++ b/tests/core/tax_scales/test_linear_average_rate_tax_scale.py @@ -1,7 +1,7 @@ import numpy import pytest -import openfisca_test as test +import openfisca_test as tools from openfisca_core import taxscales @@ -14,7 +14,7 @@ def test_bracket_indices() -> None: result = tax_scale.bracket_indices(tax_base) - test.assert_near(result, [0, 0, 0, 1, 1, 2]) + tools.assert_near(result, [0, 0, 0, 1, 1, 2]) def test_bracket_indices_with_factor() -> None: @@ -26,7 +26,7 @@ def test_bracket_indices_with_factor() -> None: result = tax_scale.bracket_indices(tax_base, factor=2.0) - test.assert_near(result, [0, 0, 0, 0, 1, 1]) + tools.assert_near(result, [0, 0, 0, 0, 1, 1]) def test_bracket_indices_with_round_decimals() -> None: @@ -38,7 +38,7 @@ def test_bracket_indices_with_round_decimals() -> None: result = tax_scale.bracket_indices(tax_base, round_decimals=0) - test.assert_near(result, [0, 0, 1, 1, 2, 2]) + tools.assert_near(result, [0, 0, 1, 1, 2, 2]) def test_bracket_indices_without_tax_base() -> None: @@ -80,8 +80,8 @@ def test_to_marginal() -> None: result = tax_scale.to_marginal() assert result.thresholds == [0, 1, 2] - test.assert_near(result.rates, [0.1, 0.3, 0.2], absolute_error_margin=0) - test.assert_near( + tools.assert_near(result.rates, [0.1, 0.3, 0.2], absolute_error_margin=0) + tools.assert_near( result.calc(tax_base), [0.1, 0.25, 0.4, 0.5], absolute_error_margin=0, diff --git a/tests/core/tax_scales/test_marginal_amount_tax_scale.py b/tests/core/tax_scales/test_marginal_amount_tax_scale.py index 9a74b55d44..073b123b82 100644 --- a/tests/core/tax_scales/test_marginal_amount_tax_scale.py +++ b/tests/core/tax_scales/test_marginal_amount_tax_scale.py @@ -1,7 +1,7 @@ from numpy import array from pytest import fixture -import openfisca_test as test +import openfisca_test as tools from openfisca_core import parameters, periods, taxscales @@ -29,7 +29,7 @@ def test_calc() -> None: result = tax_scale.calc(tax_base) - test.assert_near(result, [0, 0.23, 0.52]) + tools.assert_near(result, [0, 0.23, 0.52]) # TODO: move, as we're testing Scale, not MarginalAmountTaxScale diff --git a/tests/core/tax_scales/test_marginal_rate_tax_scale.py b/tests/core/tax_scales/test_marginal_rate_tax_scale.py index 4f470ecfbb..618e6ccb51 100644 --- a/tests/core/tax_scales/test_marginal_rate_tax_scale.py +++ b/tests/core/tax_scales/test_marginal_rate_tax_scale.py @@ -1,7 +1,7 @@ import numpy import pytest -import openfisca_test as test +import openfisca_test as tools from openfisca_core import taxscales @@ -14,7 +14,7 @@ def test_bracket_indices() -> None: result = tax_scale.bracket_indices(tax_base) - test.assert_near(result, [0, 0, 0, 1, 1, 2]) + tools.assert_near(result, [0, 0, 0, 1, 1, 2]) def test_bracket_indices_with_factor() -> None: @@ -26,7 +26,7 @@ def test_bracket_indices_with_factor() -> None: result = tax_scale.bracket_indices(tax_base, factor=2.0) - test.assert_near(result, [0, 0, 0, 0, 1, 1]) + tools.assert_near(result, [0, 0, 0, 0, 1, 1]) def test_bracket_indices_with_round_decimals() -> None: @@ -38,7 +38,7 @@ def test_bracket_indices_with_round_decimals() -> None: result = tax_scale.bracket_indices(tax_base, round_decimals=0) - test.assert_near(result, [0, 0, 1, 1, 2, 2]) + tools.assert_near(result, [0, 0, 1, 1, 2, 2]) def test_bracket_indices_without_tax_base() -> None: @@ -80,7 +80,7 @@ def test_calc() -> None: result = tax_scale.calc(tax_base) - test.assert_near( + tools.assert_near( result, [0, 0.05, 0.1, 0.2, 0.3, 0.3], absolute_error_margin=1e-10, @@ -95,7 +95,7 @@ def test_calc_without_round() -> None: result = tax_scale.calc(tax_base) - test.assert_near( + tools.assert_near( result, [10, 10.02, 10.0002, 10.06, 10.0006, 10.05, 10.0005], absolute_error_margin=1e-10, @@ -110,7 +110,7 @@ def test_calc_when_round_is_1() -> None: result = tax_scale.calc(tax_base, round_base_decimals=1) - test.assert_near( + tools.assert_near( result, [10, 10.0, 10.0, 10.1, 10.0, 10, 10.0], absolute_error_margin=1e-10, @@ -125,7 +125,7 @@ def test_calc_when_round_is_2() -> None: result = tax_scale.calc(tax_base, round_base_decimals=2) - test.assert_near( + tools.assert_near( result, [10, 10.02, 10.0, 10.06, 10.00, 10.05, 10], absolute_error_margin=1e-10, @@ -140,7 +140,7 @@ def test_calc_when_round_is_3() -> None: result = tax_scale.calc(tax_base, round_base_decimals=3) - test.assert_near( + tools.assert_near( result, [10, 10.02, 10.0, 10.06, 10.001, 10.05, 10], absolute_error_margin=1e-10, @@ -156,7 +156,7 @@ def test_marginal_rates() -> None: result = tax_scale.marginal_rates(tax_base) - test.assert_near(result, [0, 0, 0, 0.1, 0.2]) + tools.assert_near(result, [0, 0, 0, 0.1, 0.2]) def test_inverse() -> None: @@ -169,7 +169,7 @@ def test_inverse() -> None: result = tax_scale.inverse() - test.assert_near(result.calc(net_tax_base), gross_tax_base, 1e-15) + tools.assert_near(result.calc(net_tax_base), gross_tax_base, 1e-15) def test_scale_tax_scales() -> None: @@ -183,7 +183,7 @@ def test_scale_tax_scales() -> None: result = tax_scale.scale_tax_scales(tax_base_scale) - test.assert_near(result.thresholds, scaled_tax_base) + tools.assert_near(result.thresholds, scaled_tax_base) def test_inverse_scaled_marginal_tax_scales() -> None: @@ -201,7 +201,7 @@ def test_inverse_scaled_marginal_tax_scales() -> None: result = scaled_tax_scale.inverse() - test.assert_near(result.calc(scaled_net_tax_base), scaled_gross_tax_base, 1e-13) + tools.assert_near(result.calc(scaled_net_tax_base), scaled_gross_tax_base, 1e-13) def test_to_average() -> None: @@ -216,7 +216,7 @@ def test_to_average() -> None: # Note: assert_near doesn't work for inf. assert result.thresholds == [0, 1, 2, numpy.inf] assert result.rates, [0, 0, 0.05, 0.2] - test.assert_near( + tools.assert_near( result.calc(tax_base), [0, 0.0375, 0.1, 0.125], absolute_error_margin=1e-10, diff --git a/tests/core/tax_scales/test_single_amount_tax_scale.py b/tests/core/tax_scales/test_single_amount_tax_scale.py index 9e2212fea2..8b564b915d 100644 --- a/tests/core/tax_scales/test_single_amount_tax_scale.py +++ b/tests/core/tax_scales/test_single_amount_tax_scale.py @@ -1,7 +1,7 @@ import numpy from pytest import fixture -import openfisca_test as test +import openfisca_test as tools from openfisca_core import parameters, periods, taxscales @@ -33,7 +33,7 @@ def test_calc() -> None: result = tax_scale.calc(tax_base) - test.assert_near(result, [0, 0.23, 0.29]) + tools.assert_near(result, [0, 0.23, 0.29]) def test_to_dict() -> None: diff --git a/tests/core/tax_scales/test_tax_scales_commons.py b/tests/core/tax_scales/test_tax_scales_commons.py index 66044e92ab..709c8746d4 100644 --- a/tests/core/tax_scales/test_tax_scales_commons.py +++ b/tests/core/tax_scales/test_tax_scales_commons.py @@ -1,6 +1,6 @@ import pytest -import openfisca_test as test +import openfisca_test as tools from openfisca_core import parameters, taxscales @@ -28,5 +28,5 @@ def node(): def test_combine_tax_scales(node) -> None: result = taxscales.combine_tax_scales(node) - test.assert_near(result.thresholds, [0, 2000, 3000]) - test.assert_near(result.rates, [0.07, 0.12, 0.14], 1e-13) + tools.assert_near(result.thresholds, [0, 2000, 3000]) + tools.assert_near(result.rates, [0.07, 0.12, 0.14], 1e-13) diff --git a/tests/core/test_calculate_output.py b/tests/core/test_calculate_output.py index bfe3527c79..0f588d0768 100644 --- a/tests/core/test_calculate_output.py +++ b/tests/core/test_calculate_output.py @@ -2,7 +2,7 @@ from openfisca_country_template import entities, situation_examples -import openfisca_test as test +import openfisca_test as tools from openfisca_core import simulations from openfisca_core.periods import DateUnit from openfisca_core.simulations import SimulationBuilder @@ -63,7 +63,7 @@ def test_calculate_output_add(simulation) -> None: def test_calculate_output_divide(simulation) -> None: simulation.set_input("variable_with_calculate_output_divide", 2017, [12000]) - test.assert_near( + tools.assert_near( simulation.calculate_output("variable_with_calculate_output_divide", "2017-06"), 1000, ) diff --git a/tests/core/test_countries.py b/tests/core/test_countries.py index 146cd0eada..4050bb3cb6 100644 --- a/tests/core/test_countries.py +++ b/tests/core/test_countries.py @@ -1,6 +1,6 @@ import pytest -import openfisca_test as test +import openfisca_test as tools from openfisca_core import periods, populations from openfisca_core.errors import VariableNameConflictError, VariableNotFoundError from openfisca_core.periods import DateUnit @@ -13,19 +13,19 @@ @pytest.mark.parametrize("simulation", [({"salary": 2000}, PERIOD)], indirect=True) def test_input_variable(simulation) -> None: result = simulation.calculate("salary", PERIOD) - test.assert_near(result, [2000], absolute_error_margin=0.01) + tools.assert_near(result, [2000], absolute_error_margin=0.01) @pytest.mark.parametrize("simulation", [({"salary": 2000}, PERIOD)], indirect=True) def test_basic_calculation(simulation) -> None: result = simulation.calculate("income_tax", PERIOD) - test.assert_near(result, [300], absolute_error_margin=0.01) + tools.assert_near(result, [300], absolute_error_margin=0.01) @pytest.mark.parametrize("simulation", [({"salary": 24000}, PERIOD)], indirect=True) def test_calculate_add(simulation) -> None: result = simulation.calculate_add("income_tax", PERIOD) - test.assert_near(result, [3600], absolute_error_margin=0.01) + tools.assert_near(result, [3600], absolute_error_margin=0.01) @pytest.mark.parametrize( @@ -35,14 +35,14 @@ def test_calculate_add(simulation) -> None: ) def test_calculate_divide(simulation) -> None: result = simulation.calculate_divide("housing_tax", PERIOD) - test.assert_near(result, [1000 / 12.0], absolute_error_margin=0.01) + tools.assert_near(result, [1000 / 12.0], absolute_error_margin=0.01) @pytest.mark.parametrize("simulation", [({"salary": 20000}, PERIOD)], indirect=True) def test_bareme(simulation) -> None: result = simulation.calculate("social_security_contribution", PERIOD) expected = [0.02 * 6000 + 0.06 * 6400 + 0.12 * 7600] - test.assert_near(result, expected, absolute_error_margin=0.01) + tools.assert_near(result, expected, absolute_error_margin=0.01) @pytest.mark.parametrize("simulation", [({}, PERIOD)], indirect=True) diff --git a/tests/core/test_cycles.py b/tests/core/test_cycles.py index 62005db141..2daaca9a3c 100644 --- a/tests/core/test_cycles.py +++ b/tests/core/test_cycles.py @@ -2,7 +2,7 @@ from openfisca_country_template import entities -import openfisca_test as test +import openfisca_test as tools from openfisca_core import periods from openfisca_core.errors import CycleError from openfisca_core.periods import DateUnit @@ -123,7 +123,7 @@ def test_pure_cycle(simulation, reference_period) -> None: def test_spirals_result_in_default_value(simulation, reference_period) -> None: variable3 = simulation.calculate("variable3", period=reference_period) - test.assert_near(variable3, [0]) + tools.assert_near(variable3, [0]) def test_spiral_heuristic(simulation, reference_period) -> None: @@ -133,9 +133,9 @@ def test_spiral_heuristic(simulation, reference_period) -> None: "variable6", reference_period.last_month, ) - test.assert_near(variable5, [11]) - test.assert_near(variable6, [11]) - test.assert_near(variable6_last_month, [11]) + tools.assert_near(variable5, [11]) + tools.assert_near(variable6, [11]) + tools.assert_near(variable6_last_month, [11]) def test_spiral_cache(simulation, reference_period) -> None: @@ -147,4 +147,4 @@ def test_spiral_cache(simulation, reference_period) -> None: def test_cotisation_1_level(simulation, reference_period) -> None: month = reference_period.last_month cotisation = simulation.calculate("cotisation", period=month) - test.assert_near(cotisation, [0]) + tools.assert_near(cotisation, [0]) diff --git a/tests/core/test_entities.py b/tests/core/test_entities.py index 2f00d940d7..36b7adc6ae 100644 --- a/tests/core/test_entities.py +++ b/tests/core/test_entities.py @@ -2,7 +2,7 @@ from openfisca_country_template import entities, situation_examples -import openfisca_test as test +import openfisca_test as tools from openfisca_core.simulations import SimulationBuilder from openfisca_core.tools import test_runner @@ -36,12 +36,12 @@ def new_simulation(tax_benefit_system, test_case, period=MONTH): def test_role_index_and_positions(tax_benefit_system) -> None: simulation = new_simulation(tax_benefit_system, TEST_CASE) - test.assert_near(simulation.household.members_entity_id, [0, 0, 0, 0, 1, 1]) + tools.assert_near(simulation.household.members_entity_id, [0, 0, 0, 0, 1, 1]) assert ( simulation.household.members_role == [FIRST_PARENT, SECOND_PARENT, CHILD, CHILD, FIRST_PARENT, CHILD] ).all() - test.assert_near(simulation.household.members_position, [0, 1, 2, 3, 0, 1]) + tools.assert_near(simulation.household.members_position, [0, 1, 2, 3, 0, 1]) assert simulation.person.ids == ["ind0", "ind1", "ind2", "ind3", "ind4", "ind5"] assert simulation.household.ids == ["h1", "h2"] @@ -74,12 +74,12 @@ def test_entity_structure_with_constructor(tax_benefit_system) -> None: household = simulation.household - test.assert_near(household.members_entity_id, [0, 0, 1, 0, 0]) + tools.assert_near(household.members_entity_id, [0, 0, 1, 0, 0]) assert ( household.members_role == [FIRST_PARENT, SECOND_PARENT, FIRST_PARENT, CHILD, CHILD] ).all() - test.assert_near(household.members_position, [0, 1, 0, 2, 3]) + tools.assert_near(household.members_position, [0, 1, 0, 2, 3]) def test_entity_variables_with_constructor(tax_benefit_system) -> None: @@ -112,7 +112,7 @@ def test_entity_variables_with_constructor(tax_benefit_system) -> None: test_runner.yaml.safe_load(simulation_yaml), ) household = simulation.household - test.assert_near(household("rent", "2017-06"), [800, 600]) + tools.assert_near(household("rent", "2017-06"), [800, 600]) def test_person_variable_with_constructor(tax_benefit_system) -> None: @@ -148,8 +148,8 @@ def test_person_variable_with_constructor(tax_benefit_system) -> None: test_runner.yaml.safe_load(simulation_yaml), ) person = simulation.person - test.assert_near(person("salary", "2017-11"), [1500, 0, 3000, 0, 0]) - test.assert_near(person("salary", "2017-12"), [2000, 0, 4000, 0, 0]) + tools.assert_near(person("salary", "2017-11"), [1500, 0, 3000, 0, 0]) + tools.assert_near(person("salary", "2017-12"), [2000, 0, 4000, 0, 0]) def test_set_input_with_constructor(tax_benefit_system) -> None: @@ -190,14 +190,14 @@ def test_set_input_with_constructor(tax_benefit_system) -> None: test_runner.yaml.safe_load(simulation_yaml), ) person = simulation.person - test.assert_near(person("salary", "2017-12"), [2000, 0, 4000, 0, 0]) - test.assert_near(person("salary", "2017-10"), [2000, 3000, 1600, 0, 0]) + tools.assert_near(person("salary", "2017-12"), [2000, 0, 4000, 0, 0]) + tools.assert_near(person("salary", "2017-10"), [2000, 3000, 1600, 0, 0]) def test_has_role(tax_benefit_system) -> None: simulation = new_simulation(tax_benefit_system, TEST_CASE) individu = simulation.persons - test.assert_near(individu.has_role(CHILD), [False, False, True, True, False, True]) + tools.assert_near(individu.has_role(CHILD), [False, False, True, True, False, True]) def test_has_role_with_subrole(tax_benefit_system) -> None: @@ -207,7 +207,7 @@ def test_has_role_with_subrole(tax_benefit_system) -> None: individu.has_role(PARENT), [True, True, False, False, True, False], ) - test.assert_near( + tools.assert_near( individu.has_role(FIRST_PARENT), [True, False, False, False, True, False], ) @@ -227,10 +227,10 @@ def test_project(tax_benefit_system) -> None: housing_tax = household("housing_tax", YEAR) projected_housing_tax = household.project(housing_tax) - test.assert_near(projected_housing_tax, [20000, 20000, 20000, 20000, 0, 0]) + tools.assert_near(projected_housing_tax, [20000, 20000, 20000, 20000, 0, 0]) housing_tax_projected_on_parents = household.project(housing_tax, role=PARENT) - test.assert_near(housing_tax_projected_on_parents, [20000, 20000, 0, 0, 0, 0]) + tools.assert_near(housing_tax_projected_on_parents, [20000, 20000, 0, 0, 0, 0]) def test_implicit_projection(tax_benefit_system) -> None: @@ -241,7 +241,7 @@ def test_implicit_projection(tax_benefit_system) -> None: individu = simulation.person housing_tax = individu.household("housing_tax", YEAR) - test.assert_near(housing_tax, [20000, 20000, 20000, 20000, 0, 0]) + tools.assert_near(housing_tax, [20000, 20000, 20000, 20000, 0, 0]) def test_sum(tax_benefit_system) -> None: @@ -257,11 +257,11 @@ def test_sum(tax_benefit_system) -> None: salary = household.members("salary", "2016-01") total_salary_by_household = household.sum(salary) - test.assert_near(total_salary_by_household, [2500, 3500]) + tools.assert_near(total_salary_by_household, [2500, 3500]) total_salary_parents_by_household = household.sum(salary, role=PARENT) - test.assert_near(total_salary_parents_by_household, [2500, 3000]) + tools.assert_near(total_salary_parents_by_household, [2500, 3000]) def test_any(tax_benefit_system) -> None: @@ -272,11 +272,11 @@ def test_any(tax_benefit_system) -> None: age = household.members("age", period=MONTH) condition_age = age <= 18 has_household_member_with_age_inf_18 = household.any(condition_age) - test.assert_near(has_household_member_with_age_inf_18, [True, False]) + tools.assert_near(has_household_member_with_age_inf_18, [True, False]) condition_age_2 = age > 18 has_household_CHILD_with_age_sup_18 = household.any(condition_age_2, role=CHILD) - test.assert_near(has_household_CHILD_with_age_sup_18, [False, True]) + tools.assert_near(has_household_CHILD_with_age_sup_18, [False, True]) def test_all(tax_benefit_system) -> None: @@ -288,10 +288,10 @@ def test_all(tax_benefit_system) -> None: condition_age = age >= 18 all_persons_age_sup_18 = household.all(condition_age) - test.assert_near(all_persons_age_sup_18, [False, True]) + tools.assert_near(all_persons_age_sup_18, [False, True]) all_parents_age_sup_18 = household.all(condition_age, role=PARENT) - test.assert_near(all_parents_age_sup_18, [True, True]) + tools.assert_near(all_parents_age_sup_18, [True, True]) def test_max(tax_benefit_system) -> None: @@ -302,10 +302,10 @@ def test_max(tax_benefit_system) -> None: age = household.members("age", period=MONTH) age_max = household.max(age) - test.assert_near(age_max, [40, 54]) + tools.assert_near(age_max, [40, 54]) age_max_child = household.max(age, role=CHILD) - test.assert_near(age_max_child, [9, 20]) + tools.assert_near(age_max_child, [9, 20]) def test_min(tax_benefit_system) -> None: @@ -316,10 +316,10 @@ def test_min(tax_benefit_system) -> None: age = household.members("age", period=MONTH) age_min = household.min(age) - test.assert_near(age_min, [7, 20]) + tools.assert_near(age_min, [7, 20]) age_min_parents = household.min(age, role=PARENT) - test.assert_near(age_min_parents, [37, 54]) + tools.assert_near(age_min_parents, [37, 54]) def test_value_nth_person(tax_benefit_system) -> None: @@ -329,16 +329,16 @@ def test_value_nth_person(tax_benefit_system) -> None: array = household.members("age", MONTH) result0 = household.value_nth_person(0, array, default=-1) - test.assert_near(result0, [40, 54]) + tools.assert_near(result0, [40, 54]) result1 = household.value_nth_person(1, array, default=-1) - test.assert_near(result1, [37, 20]) + tools.assert_near(result1, [37, 20]) result2 = household.value_nth_person(2, array, default=-1) - test.assert_near(result2, [7, -1]) + tools.assert_near(result2, [7, -1]) result3 = household.value_nth_person(3, array, default=-1) - test.assert_near(result3, [9, -1]) + tools.assert_near(result3, [9, -1]) def test_rank(tax_benefit_system) -> None: @@ -348,14 +348,14 @@ def test_rank(tax_benefit_system) -> None: age = person("age", MONTH) # [40, 37, 7, 9, 54, 20] rank = person.get_rank(person.household, age) - test.assert_near(rank, [3, 2, 0, 1, 1, 0]) + tools.assert_near(rank, [3, 2, 0, 1, 1, 0]) rank_in_siblings = person.get_rank( person.household, -age, condition=person.has_role(entities.Household.CHILD), ) - test.assert_near(rank_in_siblings, [-1, -1, 1, 0, -1, 0]) + tools.assert_near(rank_in_siblings, [-1, -1, 1, 0, -1, 0]) def test_partner(tax_benefit_system) -> None: @@ -372,7 +372,7 @@ def test_partner(tax_benefit_system) -> None: salary_second_parent = persons.value_from_partner(salary, persons.household, PARENT) - test.assert_near(salary_second_parent, [1500, 1000, 0, 0, 0, 0]) + tools.assert_near(salary_second_parent, [1500, 1000, 0, 0, 0, 0]) def test_value_from_first_person(tax_benefit_system) -> None: @@ -388,7 +388,7 @@ def test_value_from_first_person(tax_benefit_system) -> None: salaries = household.members("salary", period=MONTH) salary_first_person = household.value_from_first_person(salaries) - test.assert_near(salary_first_person, [1000, 3000]) + tools.assert_near(salary_first_person, [1000, 3000]) def test_projectors_methods(tax_benefit_system) -> None: @@ -432,7 +432,7 @@ def test_sum_following_bug_ipp_1(tax_benefit_system) -> None: eligible_i = household.members("salary", period=MONTH) < 1500 nb_eligibles_by_household = household.sum(eligible_i, role=CHILD) - test.assert_near(nb_eligibles_by_household, [0, 2]) + tools.assert_near(nb_eligibles_by_household, [0, 2]) def test_sum_following_bug_ipp_2(tax_benefit_system) -> None: @@ -454,7 +454,7 @@ def test_sum_following_bug_ipp_2(tax_benefit_system) -> None: eligible_i = household.members("salary", period=MONTH) < 1500 nb_eligibles_by_household = household.sum(eligible_i, role=CHILD) - test.assert_near(nb_eligibles_by_household, [2, 0]) + tools.assert_near(nb_eligibles_by_household, [2, 0]) def test_get_memory_usage(tax_benefit_system) -> None: @@ -504,47 +504,47 @@ def test_unordered_persons(tax_benefit_system) -> None: # Aggregation/Projection persons -> entity - test.assert_near(household.sum(salary), [2520, 3500]) - test.assert_near(household.max(salary), [1500, 3000]) - test.assert_near(household.min(salary), [0, 500]) - test.assert_near(household.all(salary > 0), [False, True]) - test.assert_near(household.any(salary > 2000), [False, True]) - test.assert_near(household.first_person("salary", "2016-01"), [0, 3000]) - test.assert_near(household.first_parent("salary", "2016-01"), [1000, 3000]) - test.assert_near(household.second_parent("salary", "2016-01"), [1500, 0]) - test.assert_near( + tools.assert_near(household.sum(salary), [2520, 3500]) + tools.assert_near(household.max(salary), [1500, 3000]) + tools.assert_near(household.min(salary), [0, 500]) + tools.assert_near(household.all(salary > 0), [False, True]) + tools.assert_near(household.any(salary > 2000), [False, True]) + tools.assert_near(household.first_person("salary", "2016-01"), [0, 3000]) + tools.assert_near(household.first_parent("salary", "2016-01"), [1000, 3000]) + tools.assert_near(household.second_parent("salary", "2016-01"), [1500, 0]) + tools.assert_near( person.value_from_partner(salary, person.household, PARENT), [0, 0, 1000, 0, 0, 1500], ) - test.assert_near(household.sum(salary, role=PARENT), [2500, 3000]) - test.assert_near(household.sum(salary, role=CHILD), [20, 500]) - test.assert_near(household.max(salary, role=PARENT), [1500, 3000]) - test.assert_near(household.max(salary, role=CHILD), [20, 500]) - test.assert_near(household.min(salary, role=PARENT), [1000, 3000]) - test.assert_near(household.min(salary, role=CHILD), [0, 500]) - test.assert_near(household.all(salary > 0, role=PARENT), [True, True]) - test.assert_near(household.all(salary > 0, role=CHILD), [False, True]) - test.assert_near(household.any(salary < 1500, role=PARENT), [True, False]) - test.assert_near(household.any(salary > 200, role=CHILD), [False, True]) + tools.assert_near(household.sum(salary, role=PARENT), [2500, 3000]) + tools.assert_near(household.sum(salary, role=CHILD), [20, 500]) + tools.assert_near(household.max(salary, role=PARENT), [1500, 3000]) + tools.assert_near(household.max(salary, role=CHILD), [20, 500]) + tools.assert_near(household.min(salary, role=PARENT), [1000, 3000]) + tools.assert_near(household.min(salary, role=CHILD), [0, 500]) + tools.assert_near(household.all(salary > 0, role=PARENT), [True, True]) + tools.assert_near(household.all(salary > 0, role=CHILD), [False, True]) + tools.assert_near(household.any(salary < 1500, role=PARENT), [True, False]) + tools.assert_near(household.any(salary > 200, role=CHILD), [False, True]) # nb_persons - test.assert_near(household.nb_persons(), [4, 2]) - test.assert_near(household.nb_persons(role=PARENT), [2, 1]) - test.assert_near(household.nb_persons(role=CHILD), [2, 1]) + tools.assert_near(household.nb_persons(), [4, 2]) + tools.assert_near(household.nb_persons(role=PARENT), [2, 1]) + tools.assert_near(household.nb_persons(role=CHILD), [2, 1]) # Projection entity -> persons - test.assert_near( - household.project(accommodation_size), [60, 160, 160, 160, 60, 160] + tools.assert_near( + household.project(accommodation_size), [60, 160, 160, 160, 60, 160], ) - test.assert_near( - household.project(accommodation_size, role=PARENT), [60, 0, 160, 0, 0, 160] + tools.assert_near( + household.project(accommodation_size, role=PARENT), [60, 0, 160, 0, 0, 160], ) - test.assert_near( - household.project(accommodation_size, role=CHILD), [0, 160, 0, 160, 60, 0] + tools.assert_near( + household.project(accommodation_size, role=CHILD), [0, 160, 0, 160, 60, 0], ) diff --git a/tests/core/test_holders.py b/tests/core/test_holders.py index 7240e2741e..8ef459274d 100644 --- a/tests/core/test_holders.py +++ b/tests/core/test_holders.py @@ -4,7 +4,7 @@ from openfisca_country_template import situation_examples from openfisca_country_template.variables import housing -import openfisca_test as test +import openfisca_test as tools from openfisca_core import holders, periods from openfisca_core.errors import PeriodMismatchError from openfisca_core.holders import Holder @@ -205,7 +205,7 @@ def test_cache_disk(couple) -> None: data = numpy.asarray([2000, 3000]) holder.put_in_cache(data, month) stored_data = holder.get_array(month) - test.assert_near(data, stored_data) + tools.assert_near(data, stored_data) def test_known_periods(couple) -> None: diff --git a/tests/core/test_simulation_builder.py b/tests/core/test_simulation_builder.py index ae4478f90e..084f09bdeb 100644 --- a/tests/core/test_simulation_builder.py +++ b/tests/core/test_simulation_builder.py @@ -6,7 +6,7 @@ from openfisca_country_template import entities, situation_examples -import openfisca_test as test +import openfisca_test as tools from openfisca_core.errors import SituationParsingError from openfisca_core.indexed_enums import Enum from openfisca_core.periods import DateUnit @@ -117,7 +117,7 @@ def test_add_person_entity_with_values(persons) -> None: persons_json = {"Alicia": {"salary": {"2018-11": 3000}}, "Javier": {}} simulation_builder = SimulationBuilder() simulation_builder.add_person_entity(persons, persons_json) - test.assert_near(simulation_builder.get_input("salary", "2018-11"), [3000, 0]) + tools.assert_near(simulation_builder.get_input("salary", "2018-11"), [3000, 0]) def test_add_person_values_with_default_period(persons) -> None: @@ -125,7 +125,7 @@ def test_add_person_values_with_default_period(persons) -> None: simulation_builder = SimulationBuilder() simulation_builder.set_default_period("2018-11") simulation_builder.add_person_entity(persons, persons_json) - test.assert_near(simulation_builder.get_input("salary", "2018-11"), [3000, 0]) + tools.assert_near(simulation_builder.get_input("salary", "2018-11"), [3000, 0]) def test_add_person_values_with_default_period_old_syntax(persons) -> None: @@ -133,7 +133,7 @@ def test_add_person_values_with_default_period_old_syntax(persons) -> None: simulation_builder = SimulationBuilder() simulation_builder.set_default_period("month:2018-11") simulation_builder.add_person_entity(persons, persons_json) - test.assert_near(simulation_builder.get_input("salary", "2018-11"), [3000, 0]) + tools.assert_near(simulation_builder.get_input("salary", "2018-11"), [3000, 0]) def test_add_group_entity(households) -> None: @@ -329,7 +329,7 @@ def test_finalize_person_entity(persons) -> None: simulation_builder.add_person_entity(persons, persons_json) population = Population(persons) simulation_builder.finalize_variables_init(population) - test.assert_near(population.get_holder("salary").get_array("2018-11"), [3000, 0]) + tools.assert_near(population.get_holder("salary").get_array("2018-11"), [3000, 0]) assert population.count == 2 assert population.ids == ["Alicia", "Javier"] @@ -340,7 +340,7 @@ def test_canonicalize_period_keys(persons) -> None: simulation_builder.add_person_entity(persons, persons_json) population = Population(persons) simulation_builder.finalize_variables_init(population) - test.assert_near(population.get_holder("salary").get_array("2018-12"), [100]) + tools.assert_near(population.get_holder("salary").get_array("2018-12"), [100]) def test_finalize_households(tax_benefit_system) -> None: @@ -359,8 +359,8 @@ def test_finalize_households(tax_benefit_system) -> None: }, ) simulation_builder.finalize_variables_init(simulation.household) - test.assert_near(simulation.household.members_entity_id, [0, 0, 1, 1]) - test.assert_near( + tools.assert_near(simulation.household.members_entity_id, [0, 0, 1, 1]) + tools.assert_near( simulation.persons.has_role(entities.Household.PARENT), [True, True, False, True], ) @@ -639,7 +639,7 @@ def test_vectorial_input(tax_benefit_system) -> None: test_runner.yaml.safe_load(input_yaml), ) - test.assert_near(simulation.get_array("salary", "2016-10"), [12000, 20000]) + tools.assert_near(simulation.get_array("salary", "2016-10"), [12000, 20000]) simulation.calculate("income_tax", "2016-10") simulation.calculate("total_taxes", "2016-10") From 692f2b34c4cda611af6ac061089e5283ea87e3d1 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Tue, 24 Sep 2024 21:51:11 +0200 Subject: [PATCH 5/6] doc(changelog): fix bad merge --- CHANGELOG.md | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index df9c9e2d88..e6316dd0dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,6 @@ # Changelog -<<<<<<< HEAD -### 41.5.6 [#1185](https://github.com/openfisca/openfisca-core/pull/1185) - -#### Technical changes - -- Remove pre Python 3.9 syntax. -======= -## 41.6.0 [#1228](https://github.com/openfisca/openfisca-core/pull/1228) +## 42.0.0 [#1228](https://github.com/openfisca/openfisca-core/pull/1228) #### New features @@ -18,7 +11,12 @@ - Move `assert_near` to `openfisca_test`. - Add tests to `assert_near`. ->>>>>>> 9c701ebfc (chore(version): bump) + +### 41.5.6 [#1185](https://github.com/openfisca/openfisca-core/pull/1185) + +#### Technical changes + +- Remove pre Python 3.9 syntax. ### 41.5.5 [#1220](https://github.com/openfisca/openfisca-core/pull/1220) From d0b53e5a2f0af91364b4fb4ef080b85e6a956e8a Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Tue, 24 Sep 2024 21:52:38 +0200 Subject: [PATCH 6/6] chore(enums): remove leftover --- openfisca_core/indexed_enums/types.py | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 openfisca_core/indexed_enums/types.py diff --git a/openfisca_core/indexed_enums/types.py b/openfisca_core/indexed_enums/types.py deleted file mode 100644 index cbdb638f96..0000000000 --- a/openfisca_core/indexed_enums/types.py +++ /dev/null @@ -1,3 +0,0 @@ -from openfisca_core.types import Array, ArrayBytes, ArrayEnum, ArrayStr - -__all__ = ["Array", "ArrayBytes", "ArrayEnum", "ArrayStr"]