diff --git a/CHANGELOG.md b/CHANGELOG.md index f6a7f9b5dc..153dd51231 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/openfisca_core/commons/__init__.py b/openfisca_core/commons/__init__.py index db41ed1874..b3b5d8cbb2 100644 --- a/openfisca_core/commons/__init__.py +++ b/openfisca_core/commons/__init__.py @@ -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__] diff --git a/openfisca_core/commons/dummy.py b/openfisca_core/commons/dummy.py index 4136a0d429..5f1b0be330 100644 --- a/openfisca_core/commons/dummy.py +++ b/openfisca_core/commons/dummy.py @@ -2,7 +2,17 @@ class Dummy: - """A class that does nothing.""" + """A class that did nothing. + + Examples: + >>> Dummy() + None: message = [ diff --git a/openfisca_core/commons/formulas.py b/openfisca_core/commons/formulas.py index 5b49387fde..3a93477bc5 100644 --- a/openfisca_core/commons/formulas.py +++ b/openfisca_core/commons/formulas.py @@ -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]) @@ -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"] @@ -43,9 +67,21 @@ 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]) @@ -53,7 +89,7 @@ def switch(conditions, value_by_condition): >>> 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" diff --git a/openfisca_core/commons/misc.py b/openfisca_core/commons/misc.py index 5dd54f70c5..4999e6cfe4 100644 --- a/openfisca_core/commons/misc.py +++ b/openfisca_core/commons/misc.py @@ -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,), {}) @@ -20,8 +26,10 @@ def empty_clone(original): """ class Dummy(original.__class__): + """Dummy class for empty cloning.""" + def __init__(self) -> None: - pass + ... new = Dummy() new.__class__ = original.__class__ @@ -29,8 +37,14 @@ def __init__(self) -> None: 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 diff --git a/openfisca_core/commons/rates.py b/openfisca_core/commons/rates.py index 9148a1ec4d..bb94e24776 100644 --- a/openfisca_core/commons/rates.py +++ b/openfisca_core/commons/rates.py @@ -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]) @@ -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: @@ -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]) @@ -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) diff --git a/openfisca_tasks/lint.mk b/openfisca_tasks/lint.mk index a2fe4b03a0..886072bb2b 100644 --- a/openfisca_tasks/lint.mk +++ b/openfisca_tasks/lint.mk @@ -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/$* diff --git a/openfisca_tasks/publish.mk b/openfisca_tasks/publish.mk index ac0ef2c053..2bcd2c0ba7 100644 --- a/openfisca_tasks/publish.mk +++ b/openfisca_tasks/publish.mk @@ -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,$@:) diff --git a/setup.cfg b/setup.cfg index 75004da81c..213867581a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,15 +1,16 @@ -; 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 @@ -17,12 +18,9 @@ rst-directives = attribute, deprecated, seealso, versionadded, versionchang 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] diff --git a/setup.py b/setup.py index 66b49c1377..acb886dde0 100644 --- a/setup.py +++ b/setup.py @@ -39,7 +39,7 @@ setup( name = 'OpenFisca-Core', - version = '35.5.4', + version = '35.5.5', author = 'OpenFisca Team', author_email = 'contact@openfisca.org', classifiers = [