Skip to content

Commit

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

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

#### Bug Fixes

- Fix doctests of the commons module

#### Dependencies

- `darglint`, `flake8-docstrings`, & `pylint`
- For automatic docstring linting & validation.

### 35.5.3 [#1020](https://github.com/openfisca/openfisca-core/pull/1020)

#### Technical changes
Expand Down
36 changes: 24 additions & 12 deletions openfisca_core/commons/formulas.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,15 @@ def apply_thresholds(input, thresholds, choices):
"""
Return one of the choices depending on the input position compared to thresholds, for each input.
>>> apply_thresholds(np.array([4]), [5, 7], [10, 15, 20])
array([10])
>>> apply_thresholds(np.array([5]), [5, 7], [10, 15, 20])
array([10])
>>> apply_thresholds(np.array([6]), [5, 7], [10, 15, 20])
array([15])
>>> apply_thresholds(np.array([8]), [5, 7], [10, 15, 20])
array([20])
>>> apply_thresholds(np.array([10]), [5, 7, 9], [10, 15, 20])
array([0])
Examples:
>>> input = numpy.array([4, 5, 6, 7, 8])
>>> thresholds = [5, 7]
>>> choices = [10, 15, 20]
>>> apply_thresholds(input, thresholds, choices)
array([10, 10, 15, 15, 20])
"""

condlist = [input <= threshold for threshold in thresholds]
if len(condlist) == len(choices) - 1:
# If a choice is provided for input > highest threshold, last condition must be true to return it.
Expand All @@ -26,6 +24,16 @@ def apply_thresholds(input, thresholds, choices):


def concat(this, that):
"""
Examples:
>>> this = ["this", "that"]
>>> that = numpy.array([1, 2.5])
>>> concat(this, that)
array(['this1.0', 'that2.5']...)
"""

if isinstance(this, numpy.ndarray) and not numpy.issubdtype(this.dtype, numpy.str):
this = this.astype('str')
if isinstance(that, numpy.ndarray) and not numpy.issubdtype(that.dtype, numpy.str):
Expand All @@ -39,10 +47,14 @@ 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.
Example:
>>> switch(np.array([1, 1, 1, 2]), {1: 80, 2: 90})
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"
condlist = [
Expand Down
36 changes: 35 additions & 1 deletion openfisca_core/commons/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,23 @@


def empty_clone(original):
"""Create a new empty instance of the same class of the original object."""
"""Create a new empty instance of the same class of the original object.
Examples:
>>> Foo = type("Foo", (list,), {})
>>> foo = Foo([1, 2, 3])
>>> foo
[1, 2, 3]
>>> bar = empty_clone(foo)
>>> bar
[]
>>> isinstance(bar, Foo)
True
"""

class Dummy(original.__class__):
def __init__(self) -> None:
pass
Expand All @@ -15,6 +31,24 @@ def __init__(self) -> None:
def stringify_array(array: numpy.ndarray) -> str:
"""
Generate a clean string representation of a NumPY array.
Examples:
>>> import numpy
>>> stringify_array(None)
'None'
>>> array = numpy.array([10, 20.])
>>> stringify_array(array)
'[10.0, 20.0]'
>>> array = numpy.array(["10", "Twenty"])
>>> stringify_array(array)
'[10, Twenty]'
>>> array = numpy.array([list, dict(), stringify_array])
>>> stringify_array(array)
"[<class 'list'>, {}, <function stringify_array...]"
"""

if array is None:
Expand Down
20 changes: 20 additions & 0 deletions openfisca_core/commons/rates.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,16 @@ def average_rate(target = None, varying = None, trim = None):
:param target: Targeted net income, numerator
:param varying: Varying gross income, denominator
:param trim: Lower and upper bound of average rate to return
Examples:
>>> target = numpy.array([1, 2, 3])
>>> varying = [2, 2, 2]
>>> trim = [-1, .25]
>>> average_rate(target, varying, trim)
array([ nan, 0. , -0.5])
'''

average_rate = 1 - target / varying
if trim is not None:
average_rate = numpy.where(average_rate <= max(trim), average_rate, numpy.nan)
Expand All @@ -18,6 +27,17 @@ def average_rate(target = None, varying = None, trim = None):


def marginal_rate(target = None, varying = None, trim = None):
"""
Examples:
>>> target = numpy.array([1, 2, 3])
>>> varying = numpy.array([1, 2, 4])
>>> trim = [.25, .75]
>>> marginal_rate(target, varying, trim)
array([nan, 0.5])
"""

# target: numerator, varying: denominator
marginal_rate = 1 - (target[:-1] - target[1:]) / (varying[:-1] - varying[1:])
if trim is not None:
Expand Down
Empty file.
10 changes: 10 additions & 0 deletions openfisca_core/commons/tests/test_dummy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import pytest

from openfisca_core.commons import Dummy


def test_dummy_deprecation():
"""Dummy throws a deprecation warning when instantiated."""

with pytest.warns(DeprecationWarning):
assert Dummy()
81 changes: 81 additions & 0 deletions openfisca_core/commons/tests/test_formulas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import numpy
import pytest
from numpy.testing import assert_array_equal

from openfisca_core import commons


def test_apply_thresholds_when_several_inputs():
"""Makes a choice for any given input."""

input_ = numpy.array([4, 5, 6, 7, 8, 9, 10])
thresholds = [5, 7, 9]
choices = [10, 15, 20, 25]

result = commons.apply_thresholds(input_, thresholds, choices)

assert_array_equal(result, [10, 10, 15, 15, 20, 20, 25])


def test_apply_thresholds_when_too_many_thresholds():
"""Raises an AssertionError when thresholds > choices."""

input_ = numpy.array([6])
thresholds = [5, 7, 9, 11]
choices = [10, 15, 20]

with pytest.raises(AssertionError):
assert commons.apply_thresholds(input_, thresholds, choices)


def test_apply_thresholds_when_too_many_choices():
"""Raises an AssertionError when thresholds < choices - 1."""

input_ = numpy.array([6])
thresholds = [5, 7]
choices = [10, 15, 20, 25]

with pytest.raises(AssertionError):
assert commons.apply_thresholds(input_, thresholds, choices)


def test_concat_when_this_is_array_not_str():
"""Casts ``this`` to ``str`` when it is a numpy array other than string."""

this = numpy.array([1, 2])
that = numpy.array(["la", "o"])

result = commons.concat(this, that)

assert_array_equal(result, ["1la", "2o"])


def test_concat_when_that_is_array_not_str():
"""Casts ``that`` to ``str`` when it is a numpy array other than string."""

this = numpy.array(["ho", "cha"])
that = numpy.array([1, 2])

result = commons.concat(this, that)

assert_array_equal(result, ["ho1", "cha2"])


def test_concat_when_args_not_str_array_like():
"""Raises a TypeError when args are not a string array-like object."""

this = (1, 2)
that = (3, 4)

with pytest.raises(TypeError):
commons.concat(this, that)


def test_switch_when_values_are_empty():
"""Raises an AssertionError when the values are empty."""

conditions = [1, 1, 1, 2]
value_by_condition = {}

with pytest.raises(AssertionError):
assert commons.switch(conditions, value_by_condition)
26 changes: 26 additions & 0 deletions openfisca_core/commons/tests/test_rates.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import numpy
from numpy.testing import assert_array_equal

from openfisca_core import commons


def test_average_rate_when_varying_is_zero():
"""Yields infinity when the varying gross income crosses zero."""

target = numpy.array([1, 2, 3])
varying = [0, 0, 0]

result = commons.average_rate(target, varying)

assert_array_equal(result, [- numpy.inf, - numpy.inf, - numpy.inf])


def test_marginal_rate_when_varying_is_zero():
"""Yields infinity when the varying gross income crosses zero."""

target = numpy.array([1, 2, 3])
varying = numpy.array([0, 0, 0])

result = commons.marginal_rate(target, varying)

assert_array_equal(result, [numpy.inf, numpy.inf])
30 changes: 27 additions & 3 deletions openfisca_tasks/lint.mk
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
## Lint the codebase.
lint: check-syntax-errors check-style check-types
lint: check-syntax-errors check-style lint-doc check-types
@$(call print_pass,$@:)

## Compile python files to check for syntax errors.
Expand All @@ -14,10 +14,34 @@ check-style: $(shell git ls-files "*.py")
@flake8 $?
@$(call print_pass,$@:)

## Run linters to check for syntax and style errors in the doc.
lint-doc: \
lint-doc-commons \
lint-doc-types \
;

## Run linters to check for syntax and style errors in the doc.
lint-doc-%:
@## These checks are exclusively related to doc/strings/test.
@##
@## 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.
@##
@$(call print_help,$(subst $*,%,$@:))
@flake8 --select=D101,D102,D103,DAR openfisca_core/$*
@pylint openfisca_core/$*
@$(call print_pass,$@:)

## Run static type checkers for type errors.
check-types: openfisca_core openfisca_web_api
check-types:
@$(call print_help,$@:)
@mypy $?
@mypy --package openfisca_core --package openfisca_web_api
@$(call print_pass,$@:)

## Run code formatters to correct style errors.
Expand Down
2 changes: 1 addition & 1 deletion openfisca_tasks/test_code.mk
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ test-code: test-core test-country test-extension
## Run openfisca-core tests.
test-core: $(shell git ls-files "tests/*.py")
@$(call print_help,$@:)
@PYTEST_ADDOPTS="${PYTEST_ADDOPTS} --cov=openfisca_core ${pytest_args}" \
@PYTEST_ADDOPTS="${PYTEST_ADDOPTS} --cov=openfisca_core --cov=openfisca_web_api ${pytest_args}" \
openfisca test $? \
${openfisca_args}
@$(call print_pass,$@:)
Expand Down
3 changes: 3 additions & 0 deletions openfisca_tasks/test_doc.mk
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,17 @@ test-doc-checkout:
} \
|| git pull --ff-only origin master ; \
} 1> /dev/null
@$(call print_pass,$@:)

## Install doc dependencies.
test-doc-install:
@$(call print_help,$@:)
@pip install --requirement doc/requirements.txt 1> /dev/null
@pip install --editable .[dev] --upgrade 1> /dev/null
@$(call print_pass,$@:)

## Dry-build the doc.
test-doc-build:
@$(call print_help,$@:)
@sphinx-build -M dummy doc/source doc/build -n -q -W
@$(call print_pass,$@:)
Loading

0 comments on commit 29d13d1

Please sign in to comment.