Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(entities): add role builder #923

Draft
wants to merge 1 commit into
base: refactor/cleanup-entities
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 94 additions & 40 deletions openfisca_core/entities/__init__.py
Original file line number Diff line number Diff line change
@@ -1,40 +1,94 @@
# 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

from . import types
from .entity import Entity
from .group_entity import GroupEntity
from .helpers import build_entity, find_role
from .role import Role

SingleEntity = Entity

__all__ = [
"Entity",
"SingleEntity",
"GroupEntity",
"Role",
"build_entity",
"find_role",
"types",
]
"""Provides a way of representing the entities of a rule system.

Each rule system is comprised by legislation and regulations to be applied upon
"someone". In legal and economical terms, "someone" is referred to as people:
individuals, families, tax households, companies, and so on.

People can be either human or non-human, that is a legal entity, also referred
to as a legal person. Human or non-human, a person is an atomic element of a
rule system: for example, in most legislations, a salary is invariably owed
to an indivual, and payroll taxes by a company, as a juridical person. In
OpenFisca, that atomic element is represented as an :class:`.Entity`.

In other cases, legal and regulatory rules are defined for groups or clusters
of people: for example, income tax is usually due by a tax household, that is
a group of individuals. There may also be fiduciary entities where the members,
legal entities, are collectively liable for a property tax. In OpenFisca, those
cluster elements are represented as a :class:`.GroupEntity`.

In the latter case, the atomic members of a given group may have a different
:class:`Role` in the context of a specific rule: for example, income tax
is due, in some legislations, by a tax household, where we find different
roles as the declarant, the spouse, the children, and so on…

What's important is that each rule, or in OpenFisca, a :class:`.Variable`,
is defined either for an :class:`.Entity` or for a :class:`.GroupEntity`,
and in the latter case, the way the rule is going to be applied depends
on the attributes and roles of the members of the group.

Finally, there is a distiction to be made between the "abstract" entities
described by in a rule system, for example an individual, as in "any"
individual, and an actual individual, like Mauko, Andrea, Mehdi, Seiko,
or José.

This module provides tools for modelling the former. For the actual
"simulation" or "application" of any given :class:`.Variable` to a
concrete individual or group of individuals, see :class:`.Population`
and :class:`.GroupPopulation`.

Official Public API:
* :class:`.Entity`
* :class:`.GroupEntity`
* :class:`.Role`
* :func:`.build_entity`
* :func:`.check_role_validity`

Deprecated:
* :meth:`.Entity.set_tax_benefit_system`
* :meth:`.Entity.get_variable`
* :meth:`.Entity.check_variable_defined_for_entity`
* :meth:`.Entity.check_role_validity`

Note:
The ``deprecated`` features are kept so as to give time to users to
migrate, and could be definitely removed from the codebase in a future
major release.

Note:
How imports are being used today::

from openfisca_core.entities import * # Bad
from openfisca_core.entities.helpers import build_entity # Bad
from openfisca_core.entities.role import Role # Bad

The previous examples provoke cyclic dependency problems, that prevents us
from modularizing the different components of the library, so as to make
them easier to test and to maintain.

How could them be used after the next major release::

from openfisca_core import entities
from openfisca_core.entities import Role

Role() # Good: import classes as publicly exposed
entities.build_entity() # Good: use functions as publicly exposed

.. seealso:: `PEP8#Imports`_ and `OpenFisca's Styleguide`_.

.. _PEP8#Imports:
https://www.python.org/dev/peps/pep-0008/#imports

.. _OpenFisca's Styleguide:
https://github.com/openfisca/openfisca-core/blob/master/STYLEGUIDE.md

"""

# Official Public API

from .entity import Entity # noqa: F401
from .group_entity import GroupEntity # noqa: F401
from .helpers import build_entity, check_role_validity # noqa: F401
from .role import Role # noqa: F401

__all__ = ["Entity", "GroupEntity", "Role"]
__all__ = ["build_entity", "check_role_validity", *__all__]
105 changes: 105 additions & 0 deletions openfisca_core/entities/_role_builder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
from __future__ import annotations

from typing import Iterable, Optional, Sequence, Type

from openfisca_core.types import HasPlural, RoleLike, SupportsRole

import dataclasses


@dataclasses.dataclass(frozen=True)
class RoleBuilder:
"""Builds roles & sub-roles from a given input.

Attributes:
builder (:obj:`.Entity` or :obj:`.GroupEntity`):
A builder object.
buildee (:obj:`.Role`):
The objects to be built.

Args:
builder: A builder object.
buildee: The objects to be built.

Examples:
>>> from openfisca_core.entities import GroupEntity, Role

>>> group_entity = GroupEntity(
... "household",
... "households",
... "A household",
... "All the people who live together in the same place.",
... []
... )

>>> items = [{
... "key": "parent",
... "subroles": ["first_parent", "second_parent"],
... }]

>>> builder = RoleBuilder(group_entity, Role)

>>> repr(RoleBuilder)
"<class 'openfisca_core.entities._role_builder.RoleBuilder'>"

>>> repr(builder)
"<RoleBuilder(households, <class '...Role'>)>"

>>> str(builder)
"<RoleBuilder(households, <class '...Role'>)>"

>>> builder(items)
(<Role(parent)>,)

.. versionadded:: 35.7.0

"""

__slots__ = ["builder", "buildee"]
builder: HasPlural
buildee: Type[SupportsRole]

def __repr__(self) -> str:
return f"<{self.__class__.__name__}({self.builder}, {self.buildee})>"

def __call__(self, items: Iterable[RoleLike]) -> Sequence[SupportsRole]:
"""Builds a sub/role for each item in ``items``.

Args:
items: Role-like items, see :class:`.RoleLike`.

Returns:
A :obj:`list` of :obj:`.Role`.

.. versionadded:: 35.7.0

"""

return tuple(self.build(item) for item in items)

def build(self, item: RoleLike) -> SupportsRole:
"""Builds a role from ``item``.

Args:
item: A role-like item, see :class:`.RoleLike`.

Returns:
:obj:`.Role`: A :obj:`.Role`.

.. versionadded:: 35.7.0

"""

role: SupportsRole
subroles: Optional[Iterable[str]]

role = self.buildee(item, self.builder)
subroles = item.get("subroles", [])

if subroles:
role.subroles = [
self.build(RoleLike({"key": key, "max": 1})) for key in subroles
]
role.max = len(role.subroles)

return role
Loading
Loading