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

build: add nox #1161

Draft
wants to merge 7 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
*.pyc
*~
.coverage
.mypy.ini
.mypy_cache
.mypy_cache*
.noseids
.nox
.project
.pydevproject
.pytest_cache
Expand Down
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,19 @@ To run a single test:
pytest tests/core/test_parameters.py -k test_parameter_for_period
```

### Testing with Nox

In order to test several Python and NumPy versions locally, we recommend you to
install [pipx](https://pypa.github.io/pipx/installation/) and configure it with
your a Python version manager like [pyenv](https://github.com/pyenv/pyenv) and
[nox](https://nox.thea.codes/en/stable/tutorial.html#installation).

Then run:

```sh
nox -s
```

## Types

This repository relies on MyPy for optional dynamic & static type checking.
Expand Down
110 changes: 110 additions & 0 deletions noxfile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
"""Nox config file."""

import nox

nox.options.reuse_existing_virtualenvs = True
nox.options.stop_on_first_error = True


@nox.session(python = ("3.9", "3.8", "3.7"), tags = ("lint", "style"))
@nox.parametrize("numpy", ("1.23", "1.22", "1.21"))
def style(session, numpy):
"""Run tests."""

if session.python == "3.7" and numpy == "1.22":
return

session.install("--upgrade", "pip")
session.install(".[dev]")
session.install(f"numpy=={numpy}")
session.run("make", "check-style", external = True)


@nox.session(python = ("3.9", "3.8", "3.7"), tags = ("lint", "docs"))
@nox.parametrize("numpy", ("1.23", "1.22", "1.21"))
def docs(session, numpy):
"""Run tests."""

if session.python == "3.7" and numpy == "1.22":
return

session.install("--upgrade", "pip")
session.install(".[dev]")
session.install(f"numpy=={numpy}")
session.run("make", "lint-doc", external = True)


@nox.session(python = ("3.9", "3.8", "3.7"), tags = ("lint", "mypy"))
@nox.parametrize("numpy", ("1.23", "1.22", "1.21"))
def mypy(session, numpy):
"""Run tests."""

if session.python == "3.7" and numpy == "1.22":
return

session.install("--upgrade", "pip")
session.install(".[dev]")
session.install(f"numpy=={numpy}")
session.run("make", "check-types", external = True)


@nox.session(python = ("3.9", "3.8", "3.7"), tags = ("lint", "mypy-hxc"))
@nox.parametrize("numpy", ("1.23", "1.22", "1.21"))
def mypy_hxc(session, numpy):
"""Run tests."""

if session.python == "3.7" and numpy == "1.22":
return

session.install("--upgrade", "pip")
session.install(".[dev]")
session.install(f"numpy=={numpy}")
session.run("make", "lint-typing-strict", external = True)


@nox.session(python = ("3.9", "3.8", "3.7"), tags = ("test", "test-core"))
@nox.parametrize("numpy", ("1.23", "1.22", "1.21"))
def test_core(session, numpy):
"""Run tests."""

if session.python == "3.7" and numpy == "1.22":
return

session.install("--upgrade", "pip")
session.install(".[dev]")
session.install(f"numpy=={numpy}")
session.install("--no-deps", "openfisca-country-template")
session.install("--no-deps", "openfisca-extension-template")
session.run("make", "test-core", external = True)


@nox.session(python = ("3.9", "3.8", "3.7"), tags = ("test", "test-country"))
@nox.parametrize("numpy", ("1.23", "1.22", "1.21"))
def test_country(session, numpy):
"""Run tests."""

if session.python == "3.7" and numpy == "1.22":
return

session.install("--upgrade", "pip")
session.install(".[dev]")
session.install(f"numpy=={numpy}")
session.install("--no-deps", "openfisca-country-template")
session.install("--no-deps", "openfisca-extension-template")
session.run("make", "test-country", external = True)


@nox.session(python = ("3.9", "3.8", "3.7"), tags = ("test", "test-extension"))
@nox.parametrize("numpy", ("1.23", "1.22", "1.21"))
def test_extension(session, numpy):
"""Run tests."""

if session.python == "3.7" and numpy == "1.22":
return

session.install("--upgrade", "pip")
session.install(".[dev]")
session.install(f"numpy=={numpy}")
session.install("--no-deps", "openfisca-country-template")
session.install("--no-deps", "openfisca-extension-template")
session.run("make", "test-extension", external = True)
5 changes: 3 additions & 2 deletions openfisca_core/commons/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
* :func:`.average_rate`
* :func:`.concat`
* :func:`.empty_clone`
* :func:`.flatten`
* :func:`.marginal_rate`
* :func:`.stringify_array`
* :func:`.switch`
Expand Down Expand Up @@ -53,11 +54,11 @@
# Official Public API

from .formulas import apply_thresholds, concat, switch # noqa: F401
from .misc import empty_clone, stringify_array # noqa: F401
from .misc import empty_clone, flatten, 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__ = ["empty_clone", "flatten", "stringify_array", *__all__]
__all__ = ["average_rate", "marginal_rate", *__all__]

# Deprecated
Expand Down
26 changes: 11 additions & 15 deletions openfisca_core/commons/formulas.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
from typing import Any, Dict, Sequence, TypeVar
from typing import Any, Dict, Sequence, Union

import numpy

from openfisca_core.types import ArrayLike, Array

T = TypeVar("T")


def apply_thresholds(
input: Array[float],
thresholds: ArrayLike[float],
choices: ArrayLike[float],
) -> Array[float]:
input: numpy.float_,
thresholds: Sequence[float],
choices: Sequence[float],
) -> numpy.ndarray:
"""Makes a choice based on an input and thresholds.

From a list of ``choices``, this function selects one of these values
Expand Down Expand Up @@ -40,7 +36,7 @@ def apply_thresholds(

"""

condlist: Sequence[Array[bool]]
condlist: Sequence[Union[bool, numpy.bool_]]
condlist = [input <= threshold for threshold in thresholds]

if len(condlist) == len(choices) - 1:
Expand All @@ -57,7 +53,7 @@ def apply_thresholds(
return numpy.select(condlist, choices)


def concat(this: ArrayLike[str], that: ArrayLike[str]) -> Array[str]:
def concat(this: Sequence[str], that: Sequence[str]) -> numpy.ndarray:
"""Concatenates the values of two arrays.

Args:
Expand Down Expand Up @@ -90,9 +86,9 @@ def concat(this: ArrayLike[str], that: ArrayLike[str]) -> Array[str]:


def switch(
conditions: Array[Any],
value_by_condition: Dict[float, T],
) -> Array[T]:
conditions: numpy.ndarray,
value_by_condition: Dict[float, Any],
) -> numpy.ndarray:
"""Mimicks a switch statement.

Given an array of conditions, returns an array of the same size,
Expand Down Expand Up @@ -125,4 +121,4 @@ def switch(
for condition in value_by_condition.keys()
]

return numpy.select(condlist, value_by_condition.values())
return numpy.select(condlist, tuple(value_by_condition.values()))
35 changes: 30 additions & 5 deletions openfisca_core/commons/misc.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
from typing import TypeVar
from typing import Any, Iterator, Optional, Sequence, TypeVar

from openfisca_core.types import Array
import itertools

from openfisca_core import types

T = TypeVar("T")


def empty_clone(original: T) -> T:
"""Creates an empty instance of the same class of the original object.
"""Create an empty instance of the same class of the original object.

Args:
original: An object to clone.
Expand Down Expand Up @@ -43,8 +45,31 @@ def empty_clone(original: T) -> T:
return new


def stringify_array(array: Array) -> str:
"""Generates a clean string representation of a numpy array.
def flatten(seqs: Sequence[Sequence[T]]) -> Iterator[T]:
"""Flatten a sequence of sequences.

Args:
seqs: Any sequence of sequences.

Returns:
An iterator with the values.

Examples:
>>> list(flatten([(1, 2), (3, 4)]))
[1, 2, 3, 4]

>>> list(flatten(["ab", "cd"]))
['a', 'b', 'c', 'd']

.. versionadded:: 36.0.0

"""

return itertools.chain.from_iterable(seqs)


def stringify_array(array: Optional[types.Array[Any]]) -> str:
"""Generate a clean string representation of a numpy array.

Args:
array: An array.
Expand Down
57 changes: 27 additions & 30 deletions openfisca_core/commons/rates.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
from typing import Optional
from typing import Optional, Sequence

import numpy

from openfisca_core.types import ArrayLike, Array


def average_rate(
target: Array[float],
varying: ArrayLike[float],
trim: Optional[ArrayLike[float]] = None,
) -> Array[float]:
target: numpy.ndarray,
varying: Sequence[float],
trim: Optional[Sequence[float]] = None,
) -> numpy.ndarray:
"""Computes the average rate of a target net income.

Given a ``target`` net income, and according to the ``varying`` gross
Expand Down Expand Up @@ -41,32 +39,31 @@ def average_rate(

"""

average_rate: Array[float]

average_rate = 1 - target / varying
rate: numpy.ndarray
rate = 1 - target / varying

if trim is not None:

average_rate = numpy.where(
average_rate <= max(trim),
average_rate,
rate = numpy.where(
rate <= max(trim),
rate,
numpy.nan,
)

average_rate = numpy.where(
average_rate >= min(trim),
average_rate,
rate = numpy.where(
rate >= min(trim),
rate,
numpy.nan,
)

return average_rate
return rate


def marginal_rate(
target: Array[float],
varying: Array[float],
trim: Optional[ArrayLike[float]] = None,
) -> Array[float]:
target: numpy.ndarray,
varying: numpy.ndarray,
trim: Optional[numpy.ndarray] = None,
) -> numpy.ndarray:
"""Computes the marginal rate of a target net income.

Given a ``target`` net income, and according to the ``varying`` gross
Expand Down Expand Up @@ -98,26 +95,26 @@ def marginal_rate(

"""

marginal_rate: Array[float]
rate: numpy.ndarray

marginal_rate = (
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,
rate = numpy.where(
rate <= max(trim),
rate,
numpy.nan,
)

marginal_rate = numpy.where(
marginal_rate >= min(trim),
marginal_rate,
rate = numpy.where(
rate >= min(trim),
rate,
numpy.nan,
)

return marginal_rate
return rate
4 changes: 2 additions & 2 deletions openfisca_core/indexed_enums/enum_array.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def _forbidden_operation(self, other: Any) -> NoReturn:
__and__ = _forbidden_operation
__or__ = _forbidden_operation

def decode(self) -> numpy.object_:
def decode(self) -> numpy.ndarray:
"""
Return the array of enum items corresponding to self.

Expand All @@ -82,7 +82,7 @@ def decode(self) -> numpy.object_:
list(self.possible_values),
)

def decode_to_str(self) -> numpy.str_:
def decode_to_str(self) -> numpy.ndarray:
"""
Return the array of string identifiers corresponding to self.

Expand Down
Loading