Skip to content
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

test: improve commons module doctests #1033

Merged
merged 37 commits into from
Oct 7, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
307201a
Sort deps alphabetically
Sep 19, 2021
c14fd44
Add nptying for numpy type-checks
Sep 19, 2021
f4638af
Add the types subpackage
Sep 19, 2021
dcf0504
Add darglint for docstring lints
Sep 19, 2021
342fda8
Add flake8-docstrings for docstring lints
Sep 19, 2021
596e05f
Set flake8 jobs to automatic
Sep 19, 2021
28a2f49
Add commons to flake8 doctest path
Sep 19, 2021
4a33d57
Add pylint for docstring lints
Sep 19, 2021
1261113
Add coverage threshold to pytest
Sep 19, 2021
23203ca
Add doctests setup to pytest
Sep 19, 2021
8f32216
Add types to tests path
Sep 19, 2021
727344d
Update mypy to avoid false-positives
Sep 19, 2021
e9c07c9
Fix bug in make test-doc
Sep 19, 2021
39aa617
Add commons/decorators
Sep 19, 2021
757c008
Fix commons/dummy doctests
Sep 19, 2021
a75f836
Fix commons/formulas doctests
Sep 19, 2021
cc8b623
Fix commons/rates doctests
Sep 19, 2021
54a8706
Fix commons/misc doctests
Sep 19, 2021
0025f89
Add doc to commons module
Sep 19, 2021
6578561
Delete redundant tests
Sep 19, 2021
949188c
Improve wording in the commons module
Sep 23, 2021
e96dd46
Remove deprecation expiration
Sep 24, 2021
6eb0bdf
Remove decorators
Sep 27, 2021
37f1cdc
Add doc checks to setup.cfg
Sep 29, 2021
7ce2ccc
Adapt coverage config
Sep 29, 2021
7f15099
Add explanation to checks in Makefile
Sep 29, 2021
b215b30
Remove unnecessary ellipsis
Sep 29, 2021
e2e1fe9
Rollback commons module doc
Sep 29, 2021
851b770
Rollback dummy doc
Sep 29, 2021
778655d
Rollback formulas doc
Sep 29, 2021
fa398aa
Rollback misc doc
Sep 29, 2021
21de681
Rollback rates doc
Sep 29, 2021
f3b5ed3
Rollback mypy update
Sep 29, 2021
f0d1b96
Rollback add types
Sep 29, 2021
1577293
Remove typing extensions
Sep 29, 2021
f70e66f
Apply suggestions from code review
Oct 7, 2021
8e8efe4
Bump patch to 35.5.4
Sep 19, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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