Skip to content

Commit

Permalink
[1/17] Improve commons module documentation
Browse files Browse the repository at this point in the history
Merge pull request #1055 from openfisca/documentation/commons
  • Loading branch information
Mauko Quiroga authored Oct 7, 2021
2 parents 29d13d1 + fd75c50 commit b80e54d
Show file tree
Hide file tree
Showing 10 changed files with 191 additions and 58 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

### 35.5.5 [#1055](https://github.com/openfisca/openfisca-core/pull/1055)

#### Documentation

- Complete the documentation of the commons module

### 35.5.4 [#1033](https://github.com/openfisca/openfisca-core/pull/1033)

#### Bug Fixes
Expand Down
85 changes: 62 additions & 23 deletions openfisca_core/commons/__init__.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,67 @@
# 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.module import Symbol
# >>> Symbol()
#
# See: https://www.python.org/dev/peps/pep-0008/#imports
"""Common tools for contributors and users.
from .dummy import Dummy # noqa: F401
The tools in this sub-package are intended, to help both contributors
to OpenFisca Core and to country packages.
Official Public API:
* :func:`.apply_thresholds`
* :func:`.average_rate`
* :func:`.concat`
* :func:`.empty_clone`
* :func:`.marginal_rate`
* :func:`.stringify_array`
* :func:`.switch`
Deprecated:
* :class:`.Dummy`
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.commons import * # Bad
from openfisca_core.commons.formulas import switch # Bad
from openfisca_core.commons.decorators import deprecated # 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 commons
from openfisca_core.commons import deprecated
deprecated() # Good: import classes as publicly exposed
commons.switch() # 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 .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

__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__ = ["Dummy", *__all__]
12 changes: 11 additions & 1 deletion openfisca_core/commons/dummy.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,17 @@


class Dummy:
"""A class that does nothing."""
"""A class that did nothing.
Examples:
>>> Dummy()
<openfisca_core.commons.dummy.Dummy object...
.. deprecated:: 34.7.0
:class:`.Dummy` has been deprecated and it will be removed in the
future.
"""

def __init__(self) -> None:
message = [
Expand Down
50 changes: 43 additions & 7 deletions openfisca_core/commons/formulas.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,24 @@


def apply_thresholds(input, thresholds, choices):
"""
Return one of the choices depending on the input position compared to thresholds, for each input.
"""Makes a choice based on an input and thresholds.
From a list of ``choices``, this function selects one of these values based on a list
of inputs, depending on the value of each ``input`` within a list of
``thresholds``.
Args:
input: A list of inputs to make a choice from.
thresholds: A list of thresholds to choose.
choices: A list of the possible values to choose from.
Returns:
:obj:`numpy.ndarray` of :obj:`float`:
A list of the values chosen.
Raises:
:exc:`AssertionError`: When the number of ``thresholds`` (t) and the
number of choices (c) are not either t == c or t == c - 1.
Examples:
>>> input = numpy.array([4, 5, 6, 7, 8])
Expand All @@ -24,7 +40,15 @@ def apply_thresholds(input, thresholds, choices):


def concat(this, that):
"""
"""Concatenates the values of two arrays.
Args:
this: An array to concatenate.
that: Another array to concatenate.
Returns:
:obj:`numpy.ndarray` of :obj:`float`:
An array with the concatenated values.
Examples:
>>> this = ["this", "that"]
Expand All @@ -43,17 +67,29 @@ def concat(this, that):


def switch(conditions, value_by_condition):
'''
Reproduces a switch statement: given an array of conditions, return an array of the same size replacing each
condition item by the corresponding given value.
"""Mimicks a switch statement.
Given an array of conditions, returns an array of the same size,
replacing each condition item with the matching given value.
Args:
conditions: An array of conditions.
value_by_condition: Values to replace for each condition.
Returns:
:obj:`numpy.ndarray` of :obj:`float`:
An array with the replaced values.
Raises:
:exc:`AssertionError`: When ``value_by_condition`` is empty.
Examples:
>>> conditions = numpy.array([1, 1, 1, 2])
>>> value_by_condition = {1: 80, 2: 90}
>>> switch(conditions, value_by_condition)
array([80, 80, 80, 90])
'''
"""

assert len(value_by_condition) > 0, \
"switch must be called with at least one value"
Expand Down
22 changes: 18 additions & 4 deletions openfisca_core/commons/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@


def empty_clone(original):
"""Create a new empty instance of the same class of the original object.
"""Creates an empty instance of the same class of the original object.
Args:
original: An object to clone.
Returns:
The cloned, empty, object.
Examples:
>>> Foo = type("Foo", (list,), {})
Expand All @@ -20,17 +26,25 @@ def empty_clone(original):
"""

class Dummy(original.__class__):
"""Dummy class for empty cloning."""

def __init__(self) -> None:
pass
...

new = Dummy()
new.__class__ = original.__class__
return new


def stringify_array(array: numpy.ndarray) -> str:
"""
Generate a clean string representation of a NumPY array.
"""Generates a clean string representation of a numpy array.
Args:
array: An array.
Returns:
:obj:`str`:
"None" if the ``array`` is None, the stringified ``array`` otherwise.
Examples:
>>> import numpy
Expand Down
50 changes: 42 additions & 8 deletions openfisca_core/commons/rates.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,27 @@


def average_rate(target = None, varying = None, trim = None):
'''
Computes the average rate of a targeted net income, according to the varying gross income.
"""Computes the average rate of a target net income.
:param target: Targeted net income, numerator
:param varying: Varying gross income, denominator
:param trim: Lower and upper bound of average rate to return
Given a ``target`` net income, and according to the ``varying`` gross
income. Optionally, a ``trim`` can be applied consisting of the lower and
upper bounds of the average rate to be computed.
Note:
Usually, ``target`` and ``varying`` are the same size.
Args:
target: The targeted net income.
varying: The varying gross income.
trim: The lower and upper bounds of the average rate.
Returns:
:obj:`numpy.ndarray` of :obj:`float`:
The average rate for each target.
When ``trim`` is provided, values that are out of the provided bounds
are replaced by :obj:`numpy.nan`.
Examples:
>>> target = numpy.array([1, 2, 3])
Expand All @@ -16,7 +31,7 @@ def average_rate(target = None, varying = None, trim = None):
>>> average_rate(target, varying, trim)
array([ nan, 0. , -0.5])
'''
"""

average_rate = 1 - target / varying
if trim is not None:
Expand All @@ -27,7 +42,27 @@ def average_rate(target = None, varying = None, trim = None):


def marginal_rate(target = None, varying = None, trim = None):
"""
"""Computes the marginal rate of a target net income.
Given a ``target`` net income, and according to the ``varying`` gross
income. Optionally, a ``trim`` can be applied consisting of the lower and
upper bounds of the marginal rate to be computed.
Note:
Usually, ``target`` and ``varying`` are the same size.
Args:
target: The targeted net income.
varying: The varying gross income.
trim: The lower and upper bounds of the marginal rate.
Returns:
:obj:`numpy.ndarray` of :obj:`float`:
The marginal rate for each target.
When ``trim`` is provided, values that are out of the provided bounds
are replaced by :obj:`numpy.nan`.
Examples:
>>> target = numpy.array([1, 2, 3])
Expand All @@ -38,7 +73,6 @@ def marginal_rate(target = None, varying = None, trim = None):
"""

# target: numerator, varying: denominator
marginal_rate = 1 - (target[:-1] - target[1:]) / (varying[:-1] - varying[1:])
if trim is not None:
marginal_rate = numpy.where(marginal_rate <= max(trim), marginal_rate, numpy.nan)
Expand Down
7 changes: 1 addition & 6 deletions openfisca_tasks/lint.mk
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,7 @@ lint-doc-%:
@##
@## They can be integrated into setup.cfg once all checks pass.
@## The reason they're here is because otherwise we wouldn't be
@## able to integrate documentation improvements incrementally.
@##
@## D101: Each class has to have at least one doctest.
@## D102: Each public method has to have at least one doctest.
@## D103: Each public function has to have at least one doctest.
@## DARXXX: https://github.com/terrencepreilly/darglint#error-codes.
@## able to integrate documentation improvements progresively.
@##
@$(call print_help,$(subst $*,%,$@:))
@flake8 --select=D101,D102,D103,DAR openfisca_core/$*
Expand Down
1 change: 1 addition & 0 deletions openfisca_tasks/publish.mk
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ build:
@$(call print_help,$@:)
@python setup.py bdist_wheel
@find dist -name "*.whl" -exec pip install --force-reinstall {}[dev] \;
@$(call print_pass,$@:)
14 changes: 6 additions & 8 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -1,28 +1,26 @@
; DXXX: We do not (yet) check docstrings (see https://www.pydocstyle.org/en/2.1.1/error_codes.html#grouping).
; DAR101: We do not (yet) document class/function attributes/arguments.
; DAR201: We do not (yet) document method/function returns.
; C011X: We (progressively) document the code base.
; D10X: We (progressively) check docstrings (see https://www.pydocstyle.org/en/2.1.1/error_codes.html#grouping).
; DARXXX: We (progressively) check docstrings (see https://github.com/terrencepreilly/darglint#error-codes).
; E128/133: We prefer hang-closing visual indents.
; E251: We prefer `function(x = 1)` over `function(x=1)`.
; E501: We do not enforce a maximum line length.
; F403/405: We ignore * imports.
; R0401: We avoid cyclic imports —required for unit/doc tests.
; W503/504: We break lines before binary operators (Knuth's style).

[flake8]
extend-ignore = D
hang-closing = true
extend-ignore = D,DAR101,DAR201
ignore = E128,E251,F403,F405,E501,W503,W504
in-place = true
include-in-doctest = openfisca_core/commons openfisca_core/types
rst-directives = attribute, deprecated, seealso, versionadded, versionchanged
rst-roles = any, attr, class, exc, func, meth, obj
strictness = short

; C0116: We document public functions.
; R0401: We avoid cyclic imports —required for unit/doc tests.

[pylint.message_control]
disable = all
enable = C0116,R0401
enable = C0115,C0116,R0401
score = no

[tool:pytest]
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@

setup(
name = 'OpenFisca-Core',
version = '35.5.4',
version = '35.5.5',
author = 'OpenFisca Team',
author_email = '[email protected]',
classifiers = [
Expand Down

0 comments on commit b80e54d

Please sign in to comment.