-
Notifications
You must be signed in to change notification settings - Fork 82
test(entities): improve docs, doctests, and typing #1034
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
Closed
Closed
Changes from 38 commits
Commits
Show all changes
39 commits
Select commit
Hold shift + click to select a range
22d6e63
Add checks to entities
9e330fa
Make entity a dataclass
959fc7c
Add docs to Entity
5a8be59
Add slots to Entity
77eb6e1
Add typed attributes to Entity
74e62bb
Refactor __init__ in Entity
6936801
Fix repr in Entity
9d3a722
Fix str in Entity
6a739f1
Make Entity.check_role_validity
3b70c41
Add HasVariables protocol
ad51508
Add missing tbs property to Entity
447dc9b
Deprecate Entity.set_tax_benefit_system
5c7b4ca
Move Entity,check_role_validity to helpers
ce9cb6b
Add docs to entities.build_entity
6bc7246
Add docs to Role
6b0cdf6
Make SupportsRole runtime-checkable
d076b39
Add doc to GroupEntity
4604be5
Extract role building to a function
a62c4c9
Add pure flatten function to commons
e698fe4
Add slots to GroupEntity
92312c5
Fix mutability of group entity
be3753d
Move role building out of GroupEntity
5c534ad
Add the variables descriptor
6acd4a9
Add descriptor to Entity
14ff209
Add docs to Entity.get_variable
fa6de71
Add docs to entities.build_role
eaa7f37
Add exceptions to doctests
02370cc
Cleanup protocol usage in entities
e1f06fd
Fix cyclic imports 2/2
edd3d1a
Rename RoleLike to _RoleSchema
3f70799
Add missing tests
590fe5d
Fix generated doc
1d02114
Cache roles in GroupEntity
d04d0d1
Fix imports
f6b67d8
Remove unused commons.first
03860d9
Remove underused commons.flatten
845235d
Bump minor to 35.8.0
30e4fcc
Apply suggestions from code review
5f4372e
Apply suggestions from code review
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,27 +1,95 @@ | ||
# 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. | ||
bonjourmauko marked this conversation as resolved.
Show resolved
Hide resolved
|
||
# | ||
# 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 .helpers import build_entity # noqa: F401 | ||
from .role import Role # noqa: F401 | ||
from .entity import Entity # noqa: F401 | ||
from .group_entity import GroupEntity # noqa: F401 | ||
"""Provides a way of representing the entities of a rule system. | ||
bonjourmauko marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
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 distinction to be made between the "abstract" entities | ||
described 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.check_role_validity` | ||
|
||
Note: | ||
The ``deprecated`` imports are transitional, in order to ensure | ||
non-breaking changes, and could be removed from the codebase in the next | ||
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 prevent us | ||
from modularizing the different components of the library, which would make | ||
them easier to test and to maintain. | ||
|
||
How they could be used in a future 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 .helpers import ( # noqa: F401 | ||
Entity, | ||
GroupEntity, | ||
Role, | ||
build_entity, | ||
check_role_validity, | ||
) | ||
|
||
__all__ = ["Entity", "GroupEntity", "Role"] | ||
__all__ = ["build_entity", "check_role_validity", *__all__] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
from __future__ import annotations | ||
|
||
from typing import Any, Optional, Type | ||
from typing_extensions import Protocol | ||
from openfisca_core.typing import ( | ||
EntityProtocol, | ||
TaxBenefitSystemProtocol, | ||
VariableProtocol, | ||
) | ||
|
||
import functools | ||
import os | ||
|
||
doc_url = "https://openfisca.org/doc/coding-the-legislation" | ||
|
||
|
||
class _Query(Protocol): | ||
"""A dummy class to duck-type :meth:`.TaxBenefitSystem.get_variable`.""" | ||
|
||
def __call__( | ||
self, | ||
__arg1: str, | ||
__arg2: bool = False, | ||
) -> Optional["_VariableProxy"]: | ||
"""See comment above.""" | ||
|
||
|
||
class _VariableProxy: | ||
"""A `descriptor`_ to find an :obj:`.Entity`'s :obj:`.Variable`. | ||
|
||
Attributes: | ||
entity: The :obj:`.Entity` ``owner`` of the descriptor. | ||
tax_benefit_system: The :obj:`.Entity`'s :obj:`.TaxBenefitSystem`. | ||
query: The method used to query the :obj:`.TaxBenefitSystem`. | ||
|
||
Examples: | ||
>>> from openfisca_core.entities import Entity | ||
>>> from openfisca_core.taxbenefitsystems import TaxBenefitSystem | ||
>>> from openfisca_core.variables import Variable | ||
|
||
>>> entity = Entity( | ||
... "individual", | ||
... "individuals", | ||
... "An individual", | ||
... "The minimal legal entity on which a rule can be applied.", | ||
... ) | ||
|
||
>>> class Variable(Variable): | ||
... definition_period = "month" | ||
... value_type = float | ||
... entity = entity | ||
|
||
>>> tbs = TaxBenefitSystem([entity]) | ||
>>> tbs.add_variable(Variable) | ||
<openfisca_core.entities._variable_proxy.Variable... | ||
|
||
>>> entity.tax_benefit_system = tbs | ||
|
||
>>> entity.variables.get("Variable") | ||
<...Variable... | ||
|
||
>>> entity.variables.exists().get("Variable") | ||
<...Variable... | ||
|
||
>>> entity.variables.isdefined().get("Variable") | ||
<...Variable... | ||
|
||
.. _descriptor: https://docs.python.org/3/howto/descriptor.html | ||
|
||
.. versionadded:: 35.8.0 | ||
|
||
""" | ||
|
||
entity: Optional[EntityProtocol] = None | ||
tax_benefit_system: Optional[TaxBenefitSystemProtocol] = None | ||
query: _Query | ||
|
||
def __get__( | ||
self, | ||
entity: EntityProtocol, | ||
type: Type[EntityProtocol], | ||
) -> Optional[_VariableProxy]: | ||
"""Binds :meth:`.TaxBenefitSystem.get_variable`.""" | ||
|
||
self.entity = entity | ||
|
||
self.tax_benefit_system = getattr( | ||
self.entity, | ||
"tax_benefit_system", | ||
None, | ||
) | ||
|
||
if self.tax_benefit_system is None: | ||
return None | ||
|
||
self.query = self.tax_benefit_system.get_variable | ||
|
||
return self | ||
|
||
def __set__(self, entity: EntityProtocol, value: Any) -> None: | ||
NotImplemented | ||
|
||
def get(self, variable_name: str) -> Optional[VariableProtocol]: | ||
"""Runs the query for ``variable_name``, based on the options given. | ||
|
||
Args: | ||
variable_name: The :obj:`.Variable` to be found. | ||
|
||
Returns: | ||
:obj:`.Variable` or :obj:`None`: | ||
:obj:`.Variable` when the :obj:`.Variable` exists. | ||
:obj:`None` when the :attr:`.tax_benefit_system` is not set. | ||
|
||
Raises: | ||
:exc:`.VariableNotFoundError`: When :obj:`.Variable` doesn't exist. | ||
:exc:`.ValueError`: When the :obj:`.Variable` exists but is defined | ||
for another :obj:`.Entity`. | ||
|
||
.. versionadded:: 35.8.0 | ||
|
||
""" | ||
|
||
if self.entity is None: | ||
return NotImplemented | ||
|
||
return self.query(variable_name) | ||
|
||
def exists(self) -> _VariableProxy: | ||
"""Sets ``check_existence`` to ``True``.""" | ||
|
||
self.query = functools.partial( | ||
self.query, | ||
check_existence = True, | ||
) | ||
|
||
return self | ||
|
||
def isdefined(self) -> _VariableProxy: | ||
"""Checks that ``variable_name`` is defined for :attr:`.entity`.""" | ||
|
||
# We assume that we're also checking for existence. | ||
self.exists() | ||
|
||
self.query = functools.partial( | ||
self._isdefined, | ||
self.query, | ||
) | ||
|
||
return self | ||
|
||
def _isdefined(self, query: _Query, variable_name: str, **any: Any) -> Any: | ||
variable = query(variable_name) | ||
|
||
if self.entity is None: | ||
return None | ||
|
||
if variable is None: | ||
return None | ||
|
||
if variable.entity is None: | ||
return None | ||
|
||
if self.entity != variable.entity: | ||
message = os.linesep.join([ | ||
f"You tried to compute the variable '{variable_name}' for", | ||
f"the entity '{self.entity.plural}'; however the variable", | ||
f"'{variable_name}' is defined for the entity", | ||
f"'{variable.entity.plural}'. Learn more about entities", | ||
f"in our documentation: <{doc_url}/50_entities.html>.", | ||
]) | ||
|
||
raise ValueError(message) | ||
|
||
return variable |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.