Skip to content

Commit b80e54d

Browse files
author
Mauko Quiroga
authored
[1/17] Improve commons module documentation
Merge pull request #1055 from openfisca/documentation/commons
2 parents 29d13d1 + fd75c50 commit b80e54d

10 files changed

Lines changed: 191 additions & 58 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
### 35.5.5 [#1055](https://github.com/openfisca/openfisca-core/pull/1055)
4+
5+
#### Documentation
6+
7+
- Complete the documentation of the commons module
8+
39
### 35.5.4 [#1033](https://github.com/openfisca/openfisca-core/pull/1033)
410

511
#### Bug Fixes

openfisca_core/commons/__init__.py

Lines changed: 62 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,67 @@
1-
# Transitional imports to ensure non-breaking changes.
2-
# Could be deprecated in the next major release.
3-
#
4-
# How imports are being used today:
5-
#
6-
# >>> from openfisca_core.module import symbol
7-
#
8-
# The previous example provokes cyclic dependency problems
9-
# that prevent us from modularizing the different components
10-
# of the library so to make them easier to test and to maintain.
11-
#
12-
# How could them be used after the next major release:
13-
#
14-
# >>> from openfisca_core import module
15-
# >>> module.symbol()
16-
#
17-
# And for classes:
18-
#
19-
# >>> from openfisca_core.module import Symbol
20-
# >>> Symbol()
21-
#
22-
# See: https://www.python.org/dev/peps/pep-0008/#imports
1+
"""Common tools for contributors and users.
232
24-
from .dummy import Dummy # noqa: F401
3+
The tools in this sub-package are intended, to help both contributors
4+
to OpenFisca Core and to country packages.
5+
6+
Official Public API:
7+
* :func:`.apply_thresholds`
8+
* :func:`.average_rate`
9+
* :func:`.concat`
10+
* :func:`.empty_clone`
11+
* :func:`.marginal_rate`
12+
* :func:`.stringify_array`
13+
* :func:`.switch`
14+
15+
Deprecated:
16+
* :class:`.Dummy`
17+
18+
Note:
19+
The ``deprecated`` imports are transitional, in order to ensure non-breaking
20+
changes, and could be removed from the codebase in the next
21+
major release.
22+
23+
Note:
24+
How imports are being used today::
25+
26+
from openfisca_core.commons import * # Bad
27+
from openfisca_core.commons.formulas import switch # Bad
28+
from openfisca_core.commons.decorators import deprecated # Bad
29+
30+
31+
The previous examples provoke cyclic dependency problems, that prevent us
32+
from modularizing the different components of the library, which would make
33+
them easier to test and to maintain.
34+
35+
How they could be used in a future release:
36+
37+
from openfisca_core import commons
38+
from openfisca_core.commons import deprecated
39+
40+
deprecated() # Good: import classes as publicly exposed
41+
commons.switch() # Good: use functions as publicly exposed
42+
43+
.. seealso:: `PEP8#Imports`_ and `OpenFisca's Styleguide`_.
44+
45+
.. _PEP8#Imports:
46+
https://www.python.org/dev/peps/pep-0008/#imports
47+
48+
.. _OpenFisca's Styleguide:
49+
https://github.com/openfisca/openfisca-core/blob/master/STYLEGUIDE.md
50+
51+
"""
52+
53+
# Official Public API
2554

2655
from .formulas import apply_thresholds, concat, switch # noqa: F401
2756
from .misc import empty_clone, stringify_array # noqa: F401
2857
from .rates import average_rate, marginal_rate # noqa: F401
58+
59+
__all__ = ["apply_thresholds", "concat", "switch"]
60+
__all__ = ["empty_clone", "stringify_array", *__all__]
61+
__all__ = ["average_rate", "marginal_rate", *__all__]
62+
63+
# Deprecated
64+
65+
from .dummy import Dummy # noqa: F401
66+
67+
__all__ = ["Dummy", *__all__]

openfisca_core/commons/dummy.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,17 @@
22

33

44
class Dummy:
5-
"""A class that does nothing."""
5+
"""A class that did nothing.
6+
7+
Examples:
8+
>>> Dummy()
9+
<openfisca_core.commons.dummy.Dummy object...
10+
11+
.. deprecated:: 34.7.0
12+
:class:`.Dummy` has been deprecated and it will be removed in the
13+
future.
14+
15+
"""
616

717
def __init__(self) -> None:
818
message = [

openfisca_core/commons/formulas.py

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,24 @@
22

33

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

2541

2642
def concat(this, that):
27-
"""
43+
"""Concatenates the values of two arrays.
44+
45+
Args:
46+
this: An array to concatenate.
47+
that: Another array to concatenate.
48+
49+
Returns:
50+
:obj:`numpy.ndarray` of :obj:`float`:
51+
An array with the concatenated values.
2852
2953
Examples:
3054
>>> this = ["this", "that"]
@@ -43,17 +67,29 @@ def concat(this, that):
4367

4468

4569
def switch(conditions, value_by_condition):
46-
'''
47-
Reproduces a switch statement: given an array of conditions, return an array of the same size replacing each
48-
condition item by the corresponding given value.
70+
"""Mimicks a switch statement.
71+
72+
Given an array of conditions, returns an array of the same size,
73+
replacing each condition item with the matching given value.
74+
75+
Args:
76+
conditions: An array of conditions.
77+
value_by_condition: Values to replace for each condition.
78+
79+
Returns:
80+
:obj:`numpy.ndarray` of :obj:`float`:
81+
An array with the replaced values.
82+
83+
Raises:
84+
:exc:`AssertionError`: When ``value_by_condition`` is empty.
4985
5086
Examples:
5187
>>> conditions = numpy.array([1, 1, 1, 2])
5288
>>> value_by_condition = {1: 80, 2: 90}
5389
>>> switch(conditions, value_by_condition)
5490
array([80, 80, 80, 90])
5591
56-
'''
92+
"""
5793

5894
assert len(value_by_condition) > 0, \
5995
"switch must be called with at least one value"

openfisca_core/commons/misc.py

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,13 @@
22

33

44
def empty_clone(original):
5-
"""Create a new empty instance of the same class of the original object.
5+
"""Creates an empty instance of the same class of the original object.
6+
7+
Args:
8+
original: An object to clone.
9+
10+
Returns:
11+
The cloned, empty, object.
612
713
Examples:
814
>>> Foo = type("Foo", (list,), {})
@@ -20,17 +26,25 @@ def empty_clone(original):
2026
"""
2127

2228
class Dummy(original.__class__):
29+
"""Dummy class for empty cloning."""
30+
2331
def __init__(self) -> None:
24-
pass
32+
...
2533

2634
new = Dummy()
2735
new.__class__ = original.__class__
2836
return new
2937

3038

3139
def stringify_array(array: numpy.ndarray) -> str:
32-
"""
33-
Generate a clean string representation of a NumPY array.
40+
"""Generates a clean string representation of a numpy array.
41+
42+
Args:
43+
array: An array.
44+
45+
Returns:
46+
:obj:`str`:
47+
"None" if the ``array`` is None, the stringified ``array`` otherwise.
3448
3549
Examples:
3650
>>> import numpy

openfisca_core/commons/rates.py

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,27 @@
22

33

44
def average_rate(target = None, varying = None, trim = None):
5-
'''
6-
Computes the average rate of a targeted net income, according to the varying gross income.
5+
"""Computes the average rate of a target net income.
76
8-
:param target: Targeted net income, numerator
9-
:param varying: Varying gross income, denominator
10-
:param trim: Lower and upper bound of average rate to return
7+
Given a ``target`` net income, and according to the ``varying`` gross
8+
income. Optionally, a ``trim`` can be applied consisting of the lower and
9+
upper bounds of the average rate to be computed.
10+
11+
Note:
12+
Usually, ``target`` and ``varying`` are the same size.
13+
14+
Args:
15+
target: The targeted net income.
16+
varying: The varying gross income.
17+
trim: The lower and upper bounds of the average rate.
18+
19+
Returns:
20+
:obj:`numpy.ndarray` of :obj:`float`:
21+
22+
The average rate for each target.
23+
24+
When ``trim`` is provided, values that are out of the provided bounds
25+
are replaced by :obj:`numpy.nan`.
1126
1227
Examples:
1328
>>> target = numpy.array([1, 2, 3])
@@ -16,7 +31,7 @@ def average_rate(target = None, varying = None, trim = None):
1631
>>> average_rate(target, varying, trim)
1732
array([ nan, 0. , -0.5])
1833
19-
'''
34+
"""
2035

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

2843

2944
def marginal_rate(target = None, varying = None, trim = None):
30-
"""
45+
"""Computes the marginal rate of a target net income.
46+
47+
Given a ``target`` net income, and according to the ``varying`` gross
48+
income. Optionally, a ``trim`` can be applied consisting of the lower and
49+
upper bounds of the marginal rate to be computed.
50+
51+
Note:
52+
Usually, ``target`` and ``varying`` are the same size.
53+
54+
Args:
55+
target: The targeted net income.
56+
varying: The varying gross income.
57+
trim: The lower and upper bounds of the marginal rate.
58+
59+
Returns:
60+
:obj:`numpy.ndarray` of :obj:`float`:
61+
62+
The marginal rate for each target.
63+
64+
When ``trim`` is provided, values that are out of the provided bounds
65+
are replaced by :obj:`numpy.nan`.
3166
3267
Examples:
3368
>>> target = numpy.array([1, 2, 3])
@@ -38,7 +73,6 @@ def marginal_rate(target = None, varying = None, trim = None):
3873
3974
"""
4075

41-
# target: numerator, varying: denominator
4276
marginal_rate = 1 - (target[:-1] - target[1:]) / (varying[:-1] - varying[1:])
4377
if trim is not None:
4478
marginal_rate = numpy.where(marginal_rate <= max(trim), marginal_rate, numpy.nan)

openfisca_tasks/lint.mk

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,7 @@ lint-doc-%:
2626
@##
2727
@## They can be integrated into setup.cfg once all checks pass.
2828
@## The reason they're here is because otherwise we wouldn't be
29-
@## able to integrate documentation improvements incrementally.
30-
@##
31-
@## D101: Each class has to have at least one doctest.
32-
@## D102: Each public method has to have at least one doctest.
33-
@## D103: Each public function has to have at least one doctest.
34-
@## DARXXX: https://github.com/terrencepreilly/darglint#error-codes.
29+
@## able to integrate documentation improvements progresively.
3530
@##
3631
@$(call print_help,$(subst $*,%,$@:))
3732
@flake8 --select=D101,D102,D103,DAR openfisca_core/$*

openfisca_tasks/publish.mk

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ build:
55
@$(call print_help,$@:)
66
@python setup.py bdist_wheel
77
@find dist -name "*.whl" -exec pip install --force-reinstall {}[dev] \;
8+
@$(call print_pass,$@:)

setup.cfg

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,26 @@
1-
; DXXX: We do not (yet) check docstrings (see https://www.pydocstyle.org/en/2.1.1/error_codes.html#grouping).
2-
; DAR101: We do not (yet) document class/function attributes/arguments.
3-
; DAR201: We do not (yet) document method/function returns.
1+
; C011X: We (progressively) document the code base.
2+
; D10X: We (progressively) check docstrings (see https://www.pydocstyle.org/en/2.1.1/error_codes.html#grouping).
3+
; DARXXX: We (progressively) check docstrings (see https://github.com/terrencepreilly/darglint#error-codes).
44
; E128/133: We prefer hang-closing visual indents.
55
; E251: We prefer `function(x = 1)` over `function(x=1)`.
66
; E501: We do not enforce a maximum line length.
77
; F403/405: We ignore * imports.
8+
; R0401: We avoid cyclic imports —required for unit/doc tests.
89
; W503/504: We break lines before binary operators (Knuth's style).
910

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

20-
; C0116: We document public functions.
21-
; R0401: We avoid cyclic imports —required for unit/doc tests.
22-
2321
[pylint.message_control]
2422
disable = all
25-
enable = C0116,R0401
23+
enable = C0115,C0116,R0401
2624
score = no
2725

2826
[tool:pytest]

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939

4040
setup(
4141
name = 'OpenFisca-Core',
42-
version = '35.5.4',
42+
version = '35.5.5',
4343
author = 'OpenFisca Team',
4444
author_email = 'contact@openfisca.org',
4545
classifiers = [

0 commit comments

Comments
 (0)