diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f3d7d278..133b67512 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog +## 41.2.0 [#1196](https://github.com/openfisca/openfisca-core/pull/1196) + +#### New features + +- Introduce `SubRole`. + - Allows for building subroles in an explicit manner. + +#### Discussion + +Currently, roles and subroles coexist and are used interchangeably, as +instances of the `Role` data model. This is very confusing, as the only thing +they have in common is the `key` attribute, shared with every other data model +in the `entities` module. By declaring `SubRole` as an independent data model +from `Role`, we can make the distinction between the two clearer, improving +readability, maintenance, debugging, and eventual extensibility of both. + ### 41.1.2 [#1192](https://github.com/openfisca/openfisca-core/pull/1192) #### Technical changes @@ -21,12 +37,12 @@ - Make `Role` explicitly hashable. - Details: - - By introducing `__eq__`, naturally `Role` became unhashable, because - equality was calculated based on a property of `Role` - (`role.key == another_role.key`), and no longer structurally - (`"1" == "1"`). - - This changeset removes `__eq__`, as `Role` is being used downstream as a - hashable object, and adds a test to ensure `Role`'s hashability. + - By introducing `__eq__`, naturally `Role` became unhashable, because + equality was calculated based on a property of `Role` + (`role.key == another_role.key`), and no longer structurally + (`"1" == "1"`). + - This changeset removes `__eq__`, as `Role` is being used downstream as a + hashable object, and adds a test to ensure `Role`'s hashability. ### 41.0.2 [#1194](https://github.com/openfisca/openfisca-core/pull/1194) diff --git a/openfisca_core/entities/__init__.py b/openfisca_core/entities/__init__.py index c90b9d0d6..11cdd4f11 100644 --- a/openfisca_core/entities/__init__.py +++ b/openfisca_core/entities/__init__.py @@ -22,9 +22,10 @@ # See: https://www.python.org/dev/peps/pep-0008/#imports from . import typing +from ._actions import build_entity +from ._subrole import SubRole from .entity import Entity from .group_entity import GroupEntity -from .helpers import build_entity from .role import Role -__all__ = ["Entity", "GroupEntity", "Role", "build_entity", "typing"] +__all__ = ["Entity", "GroupEntity", "Role", "SubRole", "build_entity", "typing"] diff --git a/openfisca_core/entities/_actions.py b/openfisca_core/entities/_actions.py new file mode 100644 index 000000000..038d2d3db --- /dev/null +++ b/openfisca_core/entities/_actions.py @@ -0,0 +1,81 @@ +"""Actions related to the entities context.""" + +from __future__ import annotations + +from collections.abc import Iterable, Mapping +from typing import Any + +from .entity import Entity +from .group_entity import GroupEntity + + +def build_entity( + key: str, + plural: str, + label: str, + doc: str = "", + roles: Iterable[Mapping[str, Any]] | None = None, + is_person: bool = False, + class_override: Any | None = None, + containing_entities: Iterable[str] = (), +) -> Entity | GroupEntity: + """Build an Entity` or GroupEntity. + + Args: + key (str): Key to identify the Entity or GroupEntity. + plural (str): ``key``, pluralised. + label (str): A summary description. + doc (str): A full description. + roles (list) : A list of Role, if it's a GroupEntity. + is_person (bool): If is an individual, or not. + class_override: ? + containing_entities (list): Keys of contained entities. + + Returns: + Entity or GroupEntity: + Entity: When ``is_person`` is True. + GroupEntity: When ``is_person`` is False. + + Raises: + ValueError: If ``roles`` is not an Iterable. + + Examples: + >>> from openfisca_core import entities + + >>> build_entity( + ... "syndicate", + ... "syndicates", + ... "Banks loaning jointly.", + ... roles = [], + ... containing_entities = [], + ... ) + GroupEntity(syndicate) + + >>> build_entity( + ... "company", + ... "companies", + ... "A small or medium company.", + ... is_person = True, + ... ) + Entity(company) + + >>> role = entities.Role({"key": "key"}, object()) + + >>> build_entity( + ... "syndicate", + ... "syndicates", + ... "Banks loaning jointly.", + ... roles = role, + ... ) + Traceback (most recent call last): + ValueError: Invalid value 'Role(key)' for 'roles', must be an iterable. + + """ + + if is_person: + return Entity(key, plural, label, doc) + + if isinstance(roles, (list, tuple)): + return GroupEntity(key, plural, label, doc, roles, containing_entities) + + raise ValueError(f"Invalid value '{roles}' for 'roles', must be an iterable.") diff --git a/openfisca_core/entities/_description.py b/openfisca_core/entities/_description.py new file mode 100644 index 000000000..fbd5e3e0e --- /dev/null +++ b/openfisca_core/entities/_description.py @@ -0,0 +1,54 @@ +from __future__ import annotations + +import dataclasses +import textwrap + + +@dataclasses.dataclass(frozen=True) +class Description: + """A description. + + Examples: + >>> data = { + ... "key": "parent", + ... "label": "Parents", + ... "plural": "parents", + ... "doc": "\t\t\tThe one/two adults in charge of the household.", + ... } + + >>> description = Description(**data) + + >>> repr(Description) + "" + + >>> repr(description) + "Description(key='parent', plural='parents', label='Parents', ...)" + + >>> str(description) + "Description(key='parent', plural='parents', label='Parents', ...)" + + >>> {description} + {Description(key='parent', plural='parents', label='Parents', doc=...} + + >>> description.key + 'parent' + + .. versionadded:: 41.0.1 + + """ + + #: A key to identify an entity. + key: str + + #: The ``key``, pluralised. + plural: str | None = None + + #: A summary description. + label: str | None = None + + #: A full description, non-indented. + doc: str | None = None + + def __post_init__(self) -> None: + if self.doc is not None: + object.__setattr__(self, "doc", textwrap.dedent(self.doc)) diff --git a/openfisca_core/entities/_subrole.py b/openfisca_core/entities/_subrole.py new file mode 100644 index 000000000..99a5ff5e2 --- /dev/null +++ b/openfisca_core/entities/_subrole.py @@ -0,0 +1,71 @@ +from __future__ import annotations + +import dataclasses + +from .typing import Entity, GroupEntity, Role + + +@dataclasses.dataclass(frozen=True) +class SubRole: + """The sub-role of a Role. + + Each Role can be composed of one or several SubRole. For example, if you + have a Role "parent", its sub-roles could include "mother" and "father". + + Attributes: + role (Role): The Role the SubRole belongs to. + key (str): A key to identify the SubRole. + max (int): Max number of members. + + Args: + role (Role): The Role the SubRole belongs to. + key (str): A key to identify the SubRole. + + Examples: + >>> from openfisca_core import entities + + >>> entity = entities.GroupEntity("person", "", "", "", {}) + >>> role = entities.Role({"key": "sorority"}, entity) + >>> subrole = SubRole(role, "sister") + + >>> repr(SubRole) + "" + + >>> repr(subrole) + "SubRole(role=Role(sorority), key='sister', max=1)" + + >>> str(subrole) + "SubRole(role=Role(sorority), key='sister', max=1)" + + >>> {subrole} + {SubRole(role=Role(sorority), key='sister', max=1)} + + >>> subrole.entity.key + 'person' + + >>> subrole.role.key + 'sorority' + + >>> subrole.key + 'sister' + + >>> subrole.max + 1 + + .. versionadded:: 41.2.0 + + """ + + #: An id to identify the Role the SubRole belongs to. + role: Role + + #: A key to identify the SubRole. + key: str + + #: Max number of members. + max: int = 1 + + @property + def entity(self) -> Entity | GroupEntity: + """The Entity the SubRole transitively belongs to.""" + return self.role.entity diff --git a/openfisca_core/entities/entity.py b/openfisca_core/entities/entity.py index 2501cec80..865a32f97 100644 --- a/openfisca_core/entities/entity.py +++ b/openfisca_core/entities/entity.py @@ -1,30 +1,96 @@ +from __future__ import annotations + from openfisca_core.types import TaxBenefitSystem, Variable from typing import Any, Optional import os -import textwrap +from ._description import Description +from ._subrole import SubRole from .role import Role class Entity: + """Represents an entity on which calculations can be run. + + For example an individual, a company, etc. An :class:`.Entity` + represents an "abstract" atomic unit of the legislation, as in + "any individual", or "any company". + + Attributes: + description (Description): A description of the Entity. + is_person (bool): Represents an individual? Defaults to True. + + Args: + key (str): Key to identify the Entity. + plural (str): ``key``, pluralised. + label (str): A summary description. + doc (str): A full description. + + Examples: + >>> entity = Entity( + ... "individual", + ... "individuals", + ... "An individual", + ... "\t\t\tThe minimal legal entity on which a rule might be a...", + ... ) + + >>> repr(Entity) + "" + + >>> repr(entity) + 'Entity(individual)' + + >>> str(entity) + 'Entity(individual)' + + >>> {entity} + {Entity(individual)} + """ - Represents an entity (e.g. a person, a household, etc.) on which calculations can be run. - """ + + #: A description of the Entity. + description: Description + + #: Whether it represents an individual or not. + is_person: bool = True + + #: The TaxBenefitSystem of the Entity, if defined. + _tax_benefit_system: TaxBenefitSystem | None = None + + @property + def key(self) -> str: + """A key to identify the Entity.""" + return self.description.key + + @property + def plural(self) -> str | None: + """The ``key``, pluralised.""" + return self.description.plural + + @property + def label(self) -> str | None: + """A summary description.""" + return self.description.label + + @property + def doc(self) -> str | None: + """A full description, non-indented.""" + return self.description.doc def __init__(self, key: str, plural: str, label: str, doc: str) -> None: - self.key = key - self.label = label - self.plural = plural - self.doc = textwrap.dedent(doc) + self.description = Description(key, plural, label, doc) self.is_person = True - self._tax_benefit_system = None - def set_tax_benefit_system(self, tax_benefit_system: TaxBenefitSystem): + def set_tax_benefit_system(self, tax_benefit_system: TaxBenefitSystem) -> None: self._tax_benefit_system = tax_benefit_system - def check_role_validity(self, role: Any) -> None: - if role is not None and not isinstance(role, Role): + @staticmethod + def check_role_validity(role: Any) -> None: + if role is None: + return None + + if not isinstance(role, (Role, SubRole)): raise ValueError(f"{role} is not a valid role") def get_variable( @@ -32,6 +98,13 @@ def get_variable( variable_name: str, check_existence: bool = False, ) -> Optional[Variable]: + if self._tax_benefit_system is None: + message = ( + f"The entity '{self}' has no TaxBenefitSystem defined yet.", + "You should call 'set_tax_benefit_system()' first.", + ) + raise ValueError(os.linesep.join(message)) + return self._tax_benefit_system.get_variable(variable_name, check_existence) def check_variable_defined_for_entity(self, variable_name: str) -> None: @@ -52,3 +125,6 @@ def check_variable_defined_for_entity(self, variable_name: str) -> None: ".", ) raise ValueError(os.linesep.join(message)) + + def __repr__(self) -> str: + return f"{self.__class__.__name__}({self.key})" diff --git a/openfisca_core/entities/group_entity.py b/openfisca_core/entities/group_entity.py index 0c787da5b..9bb278c47 100644 --- a/openfisca_core/entities/group_entity.py +++ b/openfisca_core/entities/group_entity.py @@ -1,10 +1,14 @@ -from collections.abc import Iterable, Mapping -from typing import Any +from __future__ import annotations + +from collections.abc import Iterable from itertools import chain +from ._description import Description +from ._subrole import SubRole from .entity import Entity from .role import Role +from .typing import DescriptionParams class GroupEntity(Entity): @@ -25,32 +29,62 @@ class GroupEntity(Entity): """ # noqa RST301 + #: A description of the GroupEntity. + description: Description + + @property + def key(self) -> str: + """A key to identify the GroupEntity.""" + return self.description.key + + @property + def plural(self) -> str | None: + """The ``key``, pluralised.""" + return self.description.plural + + @property + def label(self) -> str | None: + """A summary description.""" + return self.description.label + + @property + def doc(self) -> str | None: + """A full description, non-indented.""" + return self.description.doc + def __init__( self, key: str, plural: str, label: str, doc: str, - roles: Iterable[Mapping[str, Any]], + roles: Iterable[DescriptionParams], containing_entities: Iterable[str] = (), ) -> None: - super().__init__(key, plural, label, doc) + self.description = Description(key, plural, label, doc) self.roles_description = roles - self.roles = [] - for role_description in roles: - role = Role(role_description, self) - setattr(self, role.key.upper(), role) - self.roles.append(role) - if role_description.get("subroles"): - role.subroles = [] - for subrole_key in role_description["subroles"]: - subrole = Role({"key": subrole_key, "max": 1}, self) - setattr(self, subrole.key.upper(), subrole) - role.subroles.append(subrole) - role.max = len(role.subroles) + self.roles = [self._build_role(description) for description in roles] self.flattened_roles = tuple( chain.from_iterable(role.subroles or [role] for role in self.roles) ) - self.is_person = False self.containing_entities = containing_entities + + def _build_role(self, description: DescriptionParams) -> Role: + role = Role(entity=self, description=description) + subroles = description.get("subroles") + + if subroles is not None: + role.subroles = [self._build_subrole(role, key) for key in subroles] + + if len(subroles) > 0: + role.max = len(role.subroles) + + setattr(self, role.key.upper(), role) + + return role + + def _build_subrole(self, role: Role, key: str) -> SubRole: + subrole = SubRole(role, key) + setattr(self, subrole.key.upper(), subrole) + return subrole diff --git a/openfisca_core/entities/helpers.py b/openfisca_core/entities/helpers.py deleted file mode 100644 index 43969fcd7..000000000 --- a/openfisca_core/entities/helpers.py +++ /dev/null @@ -1,20 +0,0 @@ -from .entity import Entity -from .group_entity import GroupEntity - - -def build_entity( - key, - plural, - label, - doc="", - roles=None, - is_person=False, - class_override=None, - containing_entities=(), -): - if is_person: - return Entity(key, plural, label, doc) - else: - return GroupEntity( - key, plural, label, doc, roles, containing_entities=containing_entities - ) diff --git a/openfisca_core/entities/role.py b/openfisca_core/entities/role.py index 976711b60..6a8406d5e 100644 --- a/openfisca_core/entities/role.py +++ b/openfisca_core/entities/role.py @@ -1,12 +1,9 @@ from __future__ import annotations -from collections.abc import Mapping, Sequence -from typing import Any +from collections.abc import Iterable -import dataclasses -import textwrap - -from .typing import Entity +from ._description import Description +from .typing import DescriptionParams, Entity, GroupEntity, SubRole class Role: @@ -19,7 +16,7 @@ class Role: Attributes: entity (Entity): The Entity the Role belongs to. - description (_Description): A description of the Role. + description (Description): A description of the Role. max (int): Max number of members. subroles (list[Role]): A list of subroles. @@ -28,7 +25,10 @@ class Role: entity (Entity): The Entity to which the Role belongs. Examples: - >>> role = Role({"key": "parent"}, object()) + >>> from openfisca_core import entities + + >>> entity = entities.GroupEntity("person", "", "", "", {}) + >>> role = Role({"key": "parent"}, entity) >>> repr(Role) "" @@ -48,16 +48,16 @@ class Role: """ #: The Entity the Role belongs to. - entity: Entity + entity: Entity | GroupEntity #: A description of the Role. - description: _Description + description: Description #: Max number of members. max: int | None = None #: A list of subroles. - subroles: Sequence[Role] | None = None + subroles: Iterable[SubRole] | None = None @property def key(self) -> str: @@ -79,66 +79,16 @@ def doc(self) -> str | None: """A full description, non-indented.""" return self.description.doc - def __init__(self, description: Mapping[str, Any], entity: Entity) -> None: - self.description = _Description( - **{ - key: value - for key, value in description.items() - if key in {"key", "plural", "label", "doc"} - } + def __init__( + self, description: DescriptionParams, entity: Entity | GroupEntity + ) -> None: + self.description = Description( + description["key"], + description.get("plural"), + description.get("label"), + description.get("doc"), ) self.entity = entity - self.max = description.get("max") def __repr__(self) -> str: - return f"Role({self.key})" - - -@dataclasses.dataclass(frozen=True) -class _Description: - """A Role's description. - - Examples: - >>> data = { - ... "key": "parent", - ... "label": "Parents", - ... "plural": "parents", - ... "doc": "\t\t\tThe one/two adults in charge of the household.", - ... } - - >>> description = _Description(**data) - - >>> repr(_Description) - "" - - >>> repr(description) - "_Description(key='parent', plural='parents', label='Parents', ...)" - - >>> str(description) - "_Description(key='parent', plural='parents', label='Parents', ...)" - - >>> {description} - {_Description(key='parent', plural='parents', label='Parents', doc=...} - - >>> description.key - 'parent' - - .. versionadded:: 41.0.1 - - """ - - #: A key to identify the Role. - key: str - - #: The ``key``, pluralised. - plural: str | None = None - - #: A summary description. - label: str | None = None - - #: A full description, non-indented. - doc: str | None = None - - def __post_init__(self) -> None: - if self.doc is not None: - object.__setattr__(self, "doc", textwrap.dedent(self.doc)) + return f"{self.__class__.__name__}({self.key})" diff --git a/openfisca_core/entities/tests/test_group_entity.py b/openfisca_core/entities/tests/test_group_entity.py index ed55648d7..2a1e02223 100644 --- a/openfisca_core/entities/tests/test_group_entity.py +++ b/openfisca_core/entities/tests/test_group_entity.py @@ -1,5 +1,4 @@ -from collections.abc import Mapping -from typing import Any +from openfisca_core.entities.typing import GroupEntity, DescriptionParams import pytest @@ -32,18 +31,17 @@ def third_parent() -> str: @pytest.fixture -def role(parent: str, first_parent: str, third_parent: str) -> Mapping[str, Any]: - return {"key": parent, "subroles": {first_parent, third_parent}} +def role(parent: str, first_parent: str, third_parent: str) -> DescriptionParams: + return {"key": parent, "subroles": [first_parent, third_parent]} @pytest.fixture -def group_entity(role: Mapping[str, Any]) -> entities.GroupEntity: - return entities.GroupEntity("key", "label", "plural", "doc", (role,)) +def group_entity(role: DescriptionParams) -> GroupEntity: + return entities.GroupEntity("key", "label", "plural", "doc", [role]) def test_init_when_doc_indented() -> None: """De-indent the ``doc`` attribute if it is passed at initialisation.""" - key = "\tkey" doc = "\tdoc" group_entity = entities.GroupEntity(key, "label", "plural", doc, ()) @@ -52,18 +50,16 @@ def test_init_when_doc_indented() -> None: def test_group_entity_with_roles( - group_entity: entities.GroupEntity, parent: str, uncle: str + group_entity: GroupEntity, parent: str, uncle: str ) -> None: """Assign a Role for each role-like passed as argument.""" - assert hasattr(group_entity, parent.upper()) assert not hasattr(group_entity, uncle.upper()) def test_group_entity_with_subroles( - group_entity: entities.GroupEntity, first_parent: str, second_parent: str + group_entity: GroupEntity, first_parent: str, second_parent: str ) -> None: """Assign a Role for each subrole-like passed as argument.""" - assert hasattr(group_entity, first_parent.upper()) assert not hasattr(group_entity, second_parent.upper()) diff --git a/openfisca_core/entities/tests/test_role.py b/openfisca_core/entities/tests/test_role.py index 83692e823..4ccbbb93f 100644 --- a/openfisca_core/entities/tests/test_role.py +++ b/openfisca_core/entities/tests/test_role.py @@ -1,11 +1,16 @@ from openfisca_core import entities +class TestEntity: + @property + def key(self) -> str: + return "key" + + def test_init_when_doc_indented() -> None: """De-indent the ``doc`` attribute if it is passed at initialisation.""" - key = "\tkey" doc = "\tdoc" - role = entities.Role({"key": key, "doc": doc}, object()) + role = entities.Role({"key": key, "doc": doc}, TestEntity()) assert role.key == key assert role.doc == doc.lstrip() diff --git a/openfisca_core/entities/typing.py b/openfisca_core/entities/typing.py index 5a1e60d4a..e7cdc19f9 100644 --- a/openfisca_core/entities/typing.py +++ b/openfisca_core/entities/typing.py @@ -1,5 +1,40 @@ -from typing import Protocol +from __future__ import annotations + +from abc import abstractmethod +from collections.abc import Sequence +from typing import Protocol, TypedDict + + +class DescriptionParams(TypedDict, total=False): + key: str + plural: str | None + label: str | None + doc: str | None + subroles: Sequence[str] | None class Entity(Protocol): + @property + @abstractmethod + def key(self) -> str: + ... + + +class GroupEntity(Protocol): + ... + + +class Role(Protocol): + @property + @abstractmethod + def entity(self) -> Entity | GroupEntity: + ... + + @property + @abstractmethod + def key(self) -> str: + ... + + +class SubRole(Protocol): ... diff --git a/openfisca_core/populations/group_population.py b/openfisca_core/populations/group_population.py index 3ec47fe29..8c9fb734f 100644 --- a/openfisca_core/populations/group_population.py +++ b/openfisca_core/populations/group_population.py @@ -2,8 +2,7 @@ import numpy -from openfisca_core import projectors -from openfisca_core.entities import Role +from openfisca_core import entities, projectors from openfisca_core.indexed_enums import EnumArray from .population import Population @@ -67,7 +66,7 @@ def members_role(self): return self._members_role @members_role.setter - def members_role(self, members_role: typing.Iterable[Role]): + def members_role(self, members_role: typing.Iterable[entities.Role]): if members_role is not None: self._members_role = numpy.array(list(members_role)) @@ -222,8 +221,8 @@ def nb_persons(self, role=None): If ``role`` is provided, only the entity member with the given role are taken into account. """ - if role: - if role.subroles: + if isinstance(role, (entities.Role, entities.SubRole)): + if isinstance(role, entities.Role) and role.subroles: role_condition = numpy.logical_or.reduce( [self.members_role == subrole for subrole in role.subroles] ) diff --git a/openfisca_core/populations/population.py b/openfisca_core/populations/population.py index 4aa28b2ad..22267e87b 100644 --- a/openfisca_core/populations/population.py +++ b/openfisca_core/populations/population.py @@ -1,6 +1,7 @@ from __future__ import annotations -from openfisca_core.types import Array, Entity, Period, Role, Simulation +from openfisca_core.entities.typing import Role, SubRole +from openfisca_core.types import Array, Entity, Period, Simulation from typing import Dict, NamedTuple, Optional, Sequence, Union from typing_extensions import TypedDict @@ -8,7 +9,7 @@ import numpy -from openfisca_core import periods, projectors +from openfisca_core import entities, periods, projectors from openfisca_core.holders import Holder, MemoryUsage from openfisca_core.projectors import Projector @@ -191,7 +192,7 @@ def get_memory_usage( ) @projectors.projectable - def has_role(self, role: Role) -> Optional[Array[bool]]: + def has_role(self, role: Role | SubRole) -> Optional[Array[bool]]: """ Check if a person has a given role within its `GroupEntity` @@ -204,11 +205,12 @@ def has_role(self, role: Role) -> Optional[Array[bool]]: if self.simulation is None: return None - self.entity.check_role_validity(role) + if not isinstance(role, (entities.Role, entities.SubRole)): + raise ValueError(f"{role} is not a valid role") group_population = self.simulation.get_population(role.entity.plural) - if role.subroles: + if isinstance(role, entities.Role) and role.subroles: return numpy.logical_or.reduce( [group_population.members_role == subrole for subrole in role.subroles] ) diff --git a/openfisca_core/types/__init__.py b/openfisca_core/types/__init__.py index eb403c46c..301234e47 100644 --- a/openfisca_core/types/__init__.py +++ b/openfisca_core/types/__init__.py @@ -16,7 +16,6 @@ * :attr:`.Params` * :attr:`.Period` * :attr:`.Population` - * :attr:`.Role`, * :attr:`.Simulation`, * :attr:`.TaxBenefitSystem` * :attr:`.Variable` @@ -59,7 +58,6 @@ Params, Period, Population, - Role, Simulation, TaxBenefitSystem, Variable, @@ -76,7 +74,6 @@ "Params", "Period", "Population", - "Role", "Simulation", "TaxBenefitSystem", "Variable", diff --git a/openfisca_core/types/_domain.py b/openfisca_core/types/_domain.py index 8b9022d41..fef3aad54 100644 --- a/openfisca_core/types/_domain.py +++ b/openfisca_core/types/_domain.py @@ -98,13 +98,6 @@ def get_holder(self, variable_name: Any) -> Any: """Abstract method.""" -class Role(Protocol): - """Role protocol.""" - - entity: Any - subroles: Any - - class Simulation(Protocol): """Simulation protocol.""" diff --git a/setup.cfg b/setup.cfg index 9740d15ff..b085b9e32 100644 --- a/setup.cfg +++ b/setup.cfg @@ -55,18 +55,15 @@ python_files = **/*.py testpaths = tests [mypy] -ignore_missing_imports = True -install_types = True -non_interactive = True - -[mypy-openfisca_core.commons.tests.*] -ignore_errors = True - -[mypy-openfisca_core.holders.tests.*] -ignore_errors = True - -[mypy-openfisca_core.periods.tests.*] -ignore_errors = True - -[mypy-openfisca_core.scripts.*] -ignore_errors = True +allow_redefinition = false +ignore_errors = false +ignore_missing_imports = true +implicit_reexport = false +install_types = true +local_partial_types = true +no_implicit_optional = true +non_interactive = true +strict = true +strict_optional = true +warn_no_return = true +warn_unreachable = true diff --git a/setup.py b/setup.py index 07fd5c2c9..09dc8938b 100644 --- a/setup.py +++ b/setup.py @@ -67,7 +67,7 @@ setup( name="OpenFisca-Core", - version="41.1.2", + version="41.2.0", author="OpenFisca Team", author_email="contact@openfisca.org", classifiers=[