diff --git a/CHANGELOG.md b/CHANGELOG.md index 549588caf..9b866fe9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## 43.1.0 [#1255](https://github.com/openfisca/openfisca-core/pull/1255) + +#### New features + +- Make `CoreEntity` public + - Allows for more easily creating customised entities. + +#### Technical changes + +- Add missing doctests. + # 43.0.0 [#1224](https://github.com/openfisca/openfisca-core/pull/1224) #### Technical changes diff --git a/openfisca_core/entities/__init__.py b/openfisca_core/entities/__init__.py index 9546773cb..1811e3fe9 100644 --- a/openfisca_core/entities/__init__.py +++ b/openfisca_core/entities/__init__.py @@ -1,40 +1,23 @@ -# Transitional imports to ensure non-breaking changes. -# Could be deprecated in the next major release. -# -# How imports are being used today: -# -# >>> from openfisca_core.module import symbol -# -# The previous example provokes cyclic dependency problems -# that prevent us from modularizing the different components -# of the library so to make them easier to test and to maintain. -# -# How could them be used after the next major release: -# -# >>> from openfisca_core import module -# >>> module.symbol() -# -# And for classes: -# -# >>> from openfisca_core import module -# >>> module.Symbol() -# -# See: https://www.python.org/dev/peps/pep-0008/#imports +"""Provide a way of representing the entities of a rule system.""" from . import types +from ._core_entity import CoreEntity from .entity import Entity from .group_entity import GroupEntity from .helpers import build_entity, find_role from .role import Role SingleEntity = Entity +check_role_validity = CoreEntity.check_role_validity __all__ = [ + "CoreEntity", "Entity", - "SingleEntity", "GroupEntity", "Role", + "SingleEntity", "build_entity", + "check_role_validity", "find_role", "types", ] diff --git a/openfisca_core/entities/_core_entity.py b/openfisca_core/entities/_core_entity.py index f44353e11..83af037b8 100644 --- a/openfisca_core/entities/_core_entity.py +++ b/openfisca_core/entities/_core_entity.py @@ -9,19 +9,27 @@ from .role import Role -class _CoreEntity: +class CoreEntity: """Base class to build entities from. Args: - __key: A key to identify the ``_CoreEntity``. - __plural: The ``key`` pluralised. - __label: A summary description. - __doc: A full description. - *__args: Additional arguments. + *__args: Any arguments. + **__kwargs: Any keyword arguments. + + Examples: + >>> from openfisca_core import entities + >>> from openfisca_core.entities import types as t + + >>> class Entity(entities.CoreEntity): + ... def __init__(self, key): + ... self.key = t.EntityKey(key) + + >>> Entity("individual") + Entity(individual) """ - #: A key to identify the ``_CoreEntity``. + #: A key to identify the ``CoreEntity``. key: t.EntityKey #: The ``key`` pluralised. @@ -33,27 +41,20 @@ class _CoreEntity: #: A full description. doc: str - #: Whether the ``_CoreEntity`` is a person or not. + #: Whether the ``CoreEntity`` is a person or not. is_person: ClassVar[bool] #: A ``TaxBenefitSystem`` instance. _tax_benefit_system: None | t.TaxBenefitSystem = None @abc.abstractmethod - def __init__( - self, - __key: str, - __plural: str, - __label: str, - __doc: str, - *__args: object, - ) -> None: ... + def __init__(self, *__args: object, **__kwargs: object) -> None: ... def __repr__(self) -> str: return f"{self.__class__.__name__}({self.key})" def set_tax_benefit_system(self, tax_benefit_system: t.TaxBenefitSystem) -> None: - """A ``_CoreEntity`` belongs to a ``TaxBenefitSystem``.""" + """A ``CoreEntity`` belongs to a ``TaxBenefitSystem``.""" self._tax_benefit_system = tax_benefit_system def get_variable( @@ -72,11 +73,46 @@ def get_variable( None: When the ``Variable`` doesn't exist. Raises: + ValueError: When the :attr:`_tax_benefit_system` is not set yet. ValueError: When ``check_existence`` is ``True`` and the ``Variable`` doesn't exist. - """ + Examples: + >>> from openfisca_core import ( + ... entities, + ... periods, + ... taxbenefitsystems, + ... variables, + ... ) + + >>> this = entities.SingleEntity("this", "", "", "") + >>> that = entities.SingleEntity("that", "", "", "") + + >>> this.get_variable("tax") + Traceback (most recent call last): + ValueError: You must set 'tax_benefit_system' before calling thi... + + >>> tax_benefit_system = taxbenefitsystems.TaxBenefitSystem([this]) + >>> this.set_tax_benefit_system(tax_benefit_system) + >>> this.get_variable("tax") + + >>> this.get_variable("tax", check_existence=True) + Traceback (most recent call last): + VariableNotFoundError: You tried to calculate or to set a value... + + >>> class tax(variables.Variable): + ... definition_period = periods.MONTH + ... value_type = float + ... entity = that + + >>> this._tax_benefit_system.add_variable(tax) + + + >>> this.get_variable("tax") + + + """ if self._tax_benefit_system is None: msg = "You must set 'tax_benefit_system' before calling this method." raise ValueError( @@ -90,16 +126,47 @@ def check_variable_defined_for_entity(self, variable_name: t.VariableName) -> No Args: variable_name: The ``Variable`` to be found. - Returns: - Variable: When the ``Variable`` exists. - None: When the :attr:`_tax_benefit_system` is not set. - Raises: ValueError: When the ``Variable`` exists but is defined for another ``Entity``. - """ + Examples: + >>> from openfisca_core import ( + ... entities, + ... periods, + ... taxbenefitsystems, + ... variables, + ... ) + + >>> this = entities.SingleEntity("this", "", "", "") + >>> that = entities.SingleEntity("that", "", "", "") + >>> tax_benefit_system = taxbenefitsystems.TaxBenefitSystem([that]) + >>> this.set_tax_benefit_system(tax_benefit_system) + + >>> this.check_variable_defined_for_entity("tax") + Traceback (most recent call last): + VariableNotFoundError: You tried to calculate or to set a value... + + >>> class tax(variables.Variable): + ... definition_period = periods.WEEK + ... value_type = int + ... entity = that + + >>> this._tax_benefit_system.add_variable(tax) + + + >>> this.check_variable_defined_for_entity("tax") + Traceback (most recent call last): + ValueError: You tried to compute the variable 'tax' for the enti... + + >>> tax.entity = this + >>> this._tax_benefit_system.update_variable(tax) + + + >>> this.check_variable_defined_for_entity("tax") + + """ entity: None | t.CoreEntity = None variable: None | t.Variable = self.get_variable( variable_name, @@ -132,11 +199,20 @@ def check_role_validity(role: object) -> None: Raises: ValueError: When ``role`` is not a ``Role``. - """ + Examples: + >>> from openfisca_core import entities + >>> role = entities.Role({"key": "key"}, object()) + >>> entities.check_role_validity(role) + + >>> entities.check_role_validity("hey!") + Traceback (most recent call last): + ValueError: hey! is not a valid role + + """ if role is not None and not isinstance(role, Role): msg = f"{role} is not a valid role" raise ValueError(msg) -__all__ = ["_CoreEntity"] +__all__ = ["CoreEntity"] diff --git a/openfisca_core/entities/_description.py b/openfisca_core/entities/_description.py index 78634ca27..6e2d68af1 100644 --- a/openfisca_core/entities/_description.py +++ b/openfisca_core/entities/_description.py @@ -6,7 +6,7 @@ @dataclasses.dataclass(frozen=True) class _Description: - """A ``Role``'s description. + r"""A ``Role``'s description. Examples: >>> data = { diff --git a/openfisca_core/entities/entity.py b/openfisca_core/entities/entity.py index c00191816..673aae48b 100644 --- a/openfisca_core/entities/entity.py +++ b/openfisca_core/entities/entity.py @@ -3,11 +3,11 @@ import textwrap from . import types as t -from ._core_entity import _CoreEntity +from ._core_entity import CoreEntity -class Entity(_CoreEntity): - """An entity (e.g. a person, a household) on which calculations can be run. +class Entity(CoreEntity): + r"""An entity (e.g. a person, a household) on which calculations can be run. Args: key: A key to identify the ``Entity``. @@ -15,6 +15,25 @@ class Entity(_CoreEntity): label: A summary description. doc: A full description. + Examples: + >>> from openfisca_core import entities + + >>> entity = entities.SingleEntity( + ... "individual", + ... "individuals", + ... "An individual", + ... "\t\t\tThe minimal legal entity on which a rule might be a...", + ... ) + + >>> repr(entities.SingleEntity) + "" + + >>> repr(entity) + 'Entity(individual)' + + >>> str(entity) + 'Entity(individual)' + """ #: A key to identify the ``Entity``. diff --git a/openfisca_core/entities/group_entity.py b/openfisca_core/entities/group_entity.py index 4b588567a..796da105e 100644 --- a/openfisca_core/entities/group_entity.py +++ b/openfisca_core/entities/group_entity.py @@ -7,12 +7,12 @@ from itertools import chain from . import types as t -from ._core_entity import _CoreEntity +from ._core_entity import CoreEntity from .role import Role -class GroupEntity(_CoreEntity): - """Represents an entity containing several others with different roles. +class GroupEntity(CoreEntity): + r"""Represents an entity containing several others with different roles. A ``GroupEntity`` represents an ``Entity`` containing several other entities, with different roles, and on which calculations can be run. @@ -26,6 +26,49 @@ class GroupEntity(_CoreEntity): containing_entities: The list of keys of group entities whose members are guaranteed to be a superset of this group's entities. + Examples: + >>> from openfisca_core import entities + + >>> family_roles = [ + ... { + ... "key": "parent", + ... "subroles": ["first_parent", "second_parent"], + ... } + ... ] + + >>> family = entities.GroupEntity( + ... "family", + ... "families", + ... "A family", + ... "\t\t\tAll the people somehow related living together.", + ... family_roles, + ... ) + + >>> household_roles = [ + ... { + ... "key": "partners", + ... "subroles": ["first_partner", "second_partner"], + ... } + ... ] + + >>> household = entities.GroupEntity( + ... "household", + ... "households", + ... "A household", + ... "\t\t\tAll the people who live together in the same place.", + ... household_roles, + ... (family.key,), + ... ) + + >>> repr(entities.GroupEntity) + "" + + >>> repr(household) + 'GroupEntity(household)' + + >>> str(household) + 'GroupEntity(household)' + """ #: A key to identify the ``Entity``. diff --git a/openfisca_core/entities/helpers.py b/openfisca_core/entities/helpers.py index 146ab6d25..1dcdad88a 100644 --- a/openfisca_core/entities/helpers.py +++ b/openfisca_core/entities/helpers.py @@ -40,7 +40,7 @@ def build_entity( Examples: >>> from openfisca_core import entities - >>> entity = build_entity( + >>> entity = entities.build_entity( ... "syndicate", ... "syndicates", ... "Banks loaning jointly.", @@ -50,7 +50,7 @@ def build_entity( >>> entity GroupEntity(syndicate) - >>> build_entity( + >>> entities.build_entity( ... "company", ... "companies", ... "A small or medium company.", @@ -60,7 +60,7 @@ def build_entity( >>> role = entities.Role({"key": "key"}, entity) - >>> build_entity( + >>> entities.build_entity( ... "syndicate", ... "syndicates", ... "Banks loaning jointly.", @@ -70,7 +70,6 @@ def build_entity( TypeError: 'Role' object is not subscriptable """ - if is_person: return SingleEntity(key, plural, label, doc) @@ -105,23 +104,24 @@ def find_role( None: Else ``None``. Examples: - >>> from openfisca_core.entities.types import RoleParams + >>> from openfisca_core import entities + >>> from openfisca_core.entities import types as t - >>> principal = RoleParams( + >>> principal = t.RoleParams( ... key="principal", ... label="Principal", ... doc="Person focus of a calculation in a family context.", ... max=1, ... ) - >>> partner = RoleParams( + >>> partner = t.RoleParams( ... key="partner", ... plural="partners", ... label="Partners", ... doc="Persons partners of the principal.", ... ) - >>> parent = RoleParams( + >>> parent = t.RoleParams( ... key="parent", ... plural="parents", ... label="Parents", @@ -129,7 +129,7 @@ def find_role( ... subroles=["first_parent", "second_parent"], ... ) - >>> group_entity = build_entity( + >>> group_entity = entities.build_entity( ... key="family", ... plural="families", ... label="Family", @@ -137,16 +137,16 @@ def find_role( ... roles=[principal, partner, parent], ... ) - >>> find_role(group_entity.roles, "principal", total=1) + >>> entities.find_role(group_entity.roles, "principal", total=1) Role(principal) - >>> find_role(group_entity.roles, "partner") + >>> entities.find_role(group_entity.roles, "partner") Role(partner) - >>> find_role(group_entity.roles, "parent", total=2) + >>> entities.find_role(group_entity.roles, "parent", total=2) Role(parent) - >>> find_role(group_entity.roles, "first_parent", total=1) + >>> entities.find_role(group_entity.roles, "first_parent", total=1) Role(first_parent) """ diff --git a/openfisca_core/entities/role.py b/openfisca_core/entities/role.py index e687b2604..39bd5090e 100644 --- a/openfisca_core/entities/role.py +++ b/openfisca_core/entities/role.py @@ -20,10 +20,11 @@ class Role: Examples: >>> from openfisca_core import entities + >>> entity = entities.GroupEntity("key", "plural", "label", "doc", []) >>> role = entities.Role({"key": "parent"}, entity) - >>> repr(Role) + >>> repr(entities.Role) "" >>> repr(role) diff --git a/setup.cfg b/setup.cfg index 60ac8faf0..23760bcce 100644 --- a/setup.cfg +++ b/setup.cfg @@ -12,17 +12,17 @@ convention = google docstring_style = google extend-ignore = D ignore = - B019, - E203, - E501, - F405, - E701, - E704, - RST210, - RST212, - RST213, - RST301, - RST306, + B019 + E203 + E501 + F405 + E701 + E704 + RST210 + RST212 + RST213 + RST301 + RST306 W503 in-place = true include-in-doctest = diff --git a/setup.py b/setup.py index d20cd6bb8..64fb1fe71 100644 --- a/setup.py +++ b/setup.py @@ -69,7 +69,7 @@ setup( name="OpenFisca-Core", - version="43.0.0", + version="43.1.0", author="OpenFisca Team", author_email="contact@openfisca.org", classifiers=[