Skip to content

Commit

Permalink
chore(git): merge & rebase
Browse files Browse the repository at this point in the history
  • Loading branch information
bonjourmauko committed Sep 15, 2024
1 parent 4ed9bc7 commit e226b94
Show file tree
Hide file tree
Showing 55 changed files with 1,442 additions and 606 deletions.
17 changes: 8 additions & 9 deletions openfisca_core/commons/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
to OpenFisca Core and to country packages.
Official Public API:
* :class:`.either`
* :func:`.apply_thresholds`
* :func:`.average_rate`
* :func:`.concat`
Expand Down Expand Up @@ -50,18 +51,16 @@
"""

# Official Public API
from ._adts import Either, Failure, Success
from .dummy import Dummy # Deprecated
from .formulas import apply_thresholds, concat, switch
from .misc import empty_clone, stringify_array
from .rates import average_rate, marginal_rate

from .formulas import apply_thresholds, concat, switch # noqa: F401
from .misc import empty_clone, stringify_array # noqa: F401
from .rates import average_rate, marginal_rate # noqa: F401
either = Either

__all__ = ["apply_thresholds", "concat", "switch"]
__all__ = ["empty_clone", "stringify_array", *__all__]
__all__ = ["average_rate", "marginal_rate", *__all__]

# Deprecated

from .dummy import Dummy # noqa: F401

__all__ = ["Either", "Failure", "Success", "either", *__all__]
__all__ = ["Dummy", *__all__]
116 changes: 116 additions & 0 deletions openfisca_core/commons/_adts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
"""Algebraic data types for OpenFisca.
An algebraic data type is a structured type that’s formed by composing other
types. [...] Product types allow you to have more than one value in a single
structure, at the same time. [...] Sum types are types where your value must
be one of a fixed set of options.
.. _See:
https://jrsinclair.com/articles/2019/algebraic-data-types-what-i-wish-someone-had-explained-about-functional-programming/
"""

from __future__ import annotations

from collections.abc import Callable
from typing import Generic, TypeVar, cast, final
from typing_extensions import Never

import dataclasses

#: Type variable representing an error.
E = TypeVar("E")

#: Type variable representing a value.
A = TypeVar("A")


@dataclasses.dataclass(frozen=True)
class Either(Generic[E, A]):
"""The Either monad.
The Either monad specifies the Either data type as well as several
functions that operate on top of it. The Either data type represents the
result of a computation that may fail.
"""

#: The value or state passed on.
_value: E | A

@property
@final
def is_failure(self) -> bool:
"""bool: Whether this instance represents a failure."""
return isinstance(self, Failure)

@property
@final
def is_success(self) -> bool:
"""bool: Whether this instance represents a success."""
return isinstance(self, Success)

@final
def unwrap(self) -> E | A:
"""Return the value of this instance.
Examples:
>>> Either.fail("error").unwrap()
'error'
>>> Either.succeed(1).unwrap()
1
Returns:
E | A: The value of this instance.
"""

return self._value

@final
def then(
self, f: Callable[[A], Failure[E] | Success[A]]
) -> Failure[E] | Success[A]:
"""Apply a flatMap to input stream.
Examples:
>>> Either.fail("error").then(lambda x: Either.succeed(x)).unwrap()
'error'
>>> Either.succeed(1).then(lambda x: Either.succeed(x + 1)).unwrap()
2
Args:
f: A function that takes a value and returns a new Either instance.
Returns:
_Failure[E] | _Success[A]: The result of applying f.
"""

if self.is_success:
return f(cast(A, self.unwrap()))
return Either.fail(cast(E, self.unwrap()))

@staticmethod
@final
def fail(value: E) -> Failure[E]:
"""_Failure[E]: Create a failing result."""
return Failure(value)

@staticmethod
@final
def succeed(value: A) -> Success[A]:
"""_Success[A]: Create a successful result."""
return Success(value)


@final
class Failure(Either[E, Never]):
"""A failing result in an Either ADT."""


@final
class Success(Either[Never, A]):
"""A successful result in an Either ADT."""
20 changes: 10 additions & 10 deletions openfisca_core/commons/formulas.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
from typing import Any, Dict, Sequence, TypeVar
from typing import Any, Dict, Union

from openfisca_core.types import Array, ArrayLike

import numpy

T = TypeVar("T")


def apply_thresholds(
input: Array[float],
input: Array[numpy.float_],
thresholds: ArrayLike[float],
choices: ArrayLike[float],
) -> Array[float]:
) -> Array[numpy.float_]:
"""Makes a choice based on an input and thresholds.
From a list of ``choices``, this function selects one of these values
Expand Down Expand Up @@ -40,7 +38,7 @@ def apply_thresholds(
"""

condlist: Sequence[Array[bool]]
condlist: list[Union[Array[numpy.bool_], bool]]
condlist = [input <= threshold for threshold in thresholds]

if len(condlist) == len(choices) - 1:
Expand All @@ -58,7 +56,9 @@ def apply_thresholds(
return numpy.select(condlist, choices)


def concat(this: ArrayLike[str], that: ArrayLike[str]) -> Array[str]:
def concat(
this: Union[Array[Any], ArrayLike[str]], that: Union[Array[Any], ArrayLike[str]]
) -> Array[numpy.str_]:
"""Concatenates the values of two arrays.
Args:
Expand Down Expand Up @@ -88,8 +88,8 @@ def concat(this: ArrayLike[str], that: ArrayLike[str]) -> Array[str]:

def switch(
conditions: Array[Any],
value_by_condition: Dict[float, T],
) -> Array[T]:
value_by_condition: Dict[float, Any],
) -> Array[Any]:
"""Mimicks a switch statement.
Given an array of conditions, returns an array of the same size,
Expand Down Expand Up @@ -120,4 +120,4 @@ def switch(

condlist = [conditions == condition for condition in value_by_condition.keys()]

return numpy.select(condlist, value_by_condition.values())
return numpy.select(condlist, tuple(value_by_condition.values()))
4 changes: 2 additions & 2 deletions openfisca_core/commons/misc.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import TypeVar
from typing import Any, TypeVar, Union

from openfisca_core.types import Array

Expand Down Expand Up @@ -43,7 +43,7 @@ def empty_clone(original: T) -> T:
return new


def stringify_array(array: Array) -> str:
def stringify_array(array: Union[Array[Any], None]) -> str:
"""Generates a clean string representation of a numpy array.
Args:
Expand Down
14 changes: 7 additions & 7 deletions openfisca_core/commons/rates.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@


def average_rate(
target: Array[float],
target: Array[numpy.float_],
varying: ArrayLike[float],
trim: Optional[ArrayLike[float]] = None,
) -> Array[float]:
) -> Array[numpy.float_]:
"""Computes the average rate of a target net income.
Given a ``target`` net income, and according to the ``varying`` gross
Expand Down Expand Up @@ -41,7 +41,7 @@ def average_rate(
"""

average_rate: Array[float]
average_rate: Array[numpy.float_]

average_rate = 1 - target / varying

Expand All @@ -62,10 +62,10 @@ def average_rate(


def marginal_rate(
target: Array[float],
varying: Array[float],
target: Array[numpy.float_],
varying: Array[numpy.float_],
trim: Optional[ArrayLike[float]] = None,
) -> Array[float]:
) -> Array[numpy.float_]:
"""Computes the marginal rate of a target net income.
Given a ``target`` net income, and according to the ``varying`` gross
Expand Down Expand Up @@ -97,7 +97,7 @@ def marginal_rate(
"""

marginal_rate: Array[float]
marginal_rate: Array[numpy.float_]

marginal_rate = +1 - (target[:-1] - target[1:]) / (varying[:-1] - varying[1:])

Expand Down
33 changes: 33 additions & 0 deletions openfisca_core/commons/tests/test_adts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import pytest

from openfisca_core import commons


@pytest.fixture
def failure():
return commons.either.fail("error")


@pytest.fixture
def success():
return commons.either.succeed(1)


def test_either_is_failure(failure):
assert failure.is_failure


def test_either_is_success(success):
assert success.is_success


def test_either_unwrap(failure):
assert failure.unwrap() == "error"


def test_either_then(failure, success):
assert failure.then(lambda x: failure).unwrap() == "error"
assert failure.then(lambda x: success).unwrap() == "error"
assert success.then(lambda x: failure).unwrap() == "error"
assert success.then(lambda x: success).unwrap() == 1
assert success.then(lambda x: commons.either.succeed(x + 1)).unwrap() == 2
7 changes: 3 additions & 4 deletions openfisca_core/data_storage/in_memory_storage.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import numpy

from openfisca_core import periods
from openfisca_core.periods import DateUnit


class InMemoryStorage:
Expand All @@ -15,7 +14,7 @@ def __init__(self, is_eternal=False):

def get(self, period):
if self.is_eternal:
period = periods.period(DateUnit.ETERNITY)
period = periods.period(periods.ETERNITY)
period = periods.period(period)

values = self._arrays.get(period)
Expand All @@ -25,7 +24,7 @@ def get(self, period):

def put(self, value, period):
if self.is_eternal:
period = periods.period(DateUnit.ETERNITY)
period = periods.period(periods.ETERNITY)
period = periods.period(period)

self._arrays[period] = value
Expand All @@ -36,7 +35,7 @@ def delete(self, period=None):
return

if self.is_eternal:
period = periods.period(DateUnit.ETERNITY)
period = periods.period(periods.ETERNITY)
period = periods.period(period)

self._arrays = {
Expand Down
7 changes: 3 additions & 4 deletions openfisca_core/data_storage/on_disk_storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

from openfisca_core import periods
from openfisca_core.indexed_enums import EnumArray
from openfisca_core.periods import DateUnit


class OnDiskStorage:
Expand All @@ -29,7 +28,7 @@ def _decode_file(self, file):

def get(self, period):
if self.is_eternal:
period = periods.period(DateUnit.ETERNITY)
period = periods.period(periods.ETERNITY)
period = periods.period(period)

values = self._files.get(period)
Expand All @@ -39,7 +38,7 @@ def get(self, period):

def put(self, value, period):
if self.is_eternal:
period = periods.period(DateUnit.ETERNITY)
period = periods.period(periods.ETERNITY)
period = periods.period(period)

filename = str(period)
Expand All @@ -56,7 +55,7 @@ def delete(self, period=None):
return

if self.is_eternal:
period = periods.period(DateUnit.ETERNITY)
period = periods.period(periods.ETERNITY)
period = periods.period(period)

if period is not None:
Expand Down
12 changes: 11 additions & 1 deletion openfisca_core/entities/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,14 @@
from .helpers import build_entity, find_role
from .role import Role

__all__ = ["Entity", "GroupEntity", "Role", "build_entity", "find_role", "types"]
SingleEntity = Entity

__all__ = [
"Entity",
"SingleEntity",
"GroupEntity",
"Role",
"build_entity",
"find_role",
"types",
]
4 changes: 4 additions & 0 deletions openfisca_core/entities/_core_entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ def get_variable(
check_existence: bool = False,
) -> Variable | None:
"""Get a ``variable_name`` from ``variables``."""
if self._tax_benefit_system is None:
raise ValueError(
"You must set 'tax_benefit_system' before calling this method."
)
return self._tax_benefit_system.get_variable(variable_name, check_existence)

def check_variable_defined_for_entity(self, variable_name: str) -> None:
Expand Down
Loading

0 comments on commit e226b94

Please sign in to comment.