From 0ea77def7ad3b506c3168920b3e177f974ac82d6 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Sat, 5 Oct 2024 03:29:42 +0200 Subject: [PATCH 1/6] docs(enums): add docs to Enum (#1271) --- openfisca_core/indexed_enums/__init__.py | 34 +++++++----------- openfisca_core/indexed_enums/enum.py | 44 +++++++++++++++--------- 2 files changed, 39 insertions(+), 39 deletions(-) diff --git a/openfisca_core/indexed_enums/__init__.py b/openfisca_core/indexed_enums/__init__.py index 9c4ff7dd6..70e2d3549 100644 --- a/openfisca_core/indexed_enums/__init__.py +++ b/openfisca_core/indexed_enums/__init__.py @@ -1,25 +1,15 @@ -# 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 +"""Enumerations for variables with a limited set of possible values. + +These include: + * Highest academic level: high school, associate degree, bachelor's degree, + master's degree, doctorate… + * A household housing occupancy status: owner, tenant, free-lodger, + homeless… + * The main occupation of a person: employee, freelancer, retired, student, + unemployed… + * Etc. + +""" from . import types from .config import ENUM_ARRAY_DTYPE diff --git a/openfisca_core/indexed_enums/enum.py b/openfisca_core/indexed_enums/enum.py index a6fd5d7f9..d76c9f9e2 100644 --- a/openfisca_core/indexed_enums/enum.py +++ b/openfisca_core/indexed_enums/enum.py @@ -8,22 +8,34 @@ class Enum(t.Enum): - """Enum based on `enum34 `_, whose items - have an index. + """Enum based on `enum34 `_. + + Its items have an :any:`int` index. This is useful and performant when + running simulations on large populations. + """ - # Tweak enums to add an index attribute to each enum item - def __init__(self, name: str) -> None: - # When the enum item is initialized, self._member_names_ contains the - # names of the previously initialized items, so its length is the index - # of this item. + #: The ``index`` of the ``Enum`` member. + index: int + + def __init__(self, *__args: object, **__kwargs: object) -> None: + """Tweak :any:`~enum.Enum` to add an index to each enum item. + + When the enum is initialised, ``_member_names_`` contains the names of + the already initialized items, so its length is the index of this item. + + Args: + *__args: Positional arguments. + **__kwargs: Keyword arguments. + + """ + self.index = len(self._member_names_) - # Bypass the slow Enum.__eq__ + #: Bypass the slow :any:`~enum.Enum.__eq__` method. __eq__ = object.__eq__ - # In Python 3, __hash__ must be defined if __eq__ is defined to stay - # hashable. + #: :meth:`.__hash__` must also be defined so as to stay hashable. __hash__ = object.__hash__ @classmethod @@ -31,15 +43,13 @@ def encode( cls, array: EnumArray | numpy.int32 | numpy.float32 | numpy.object_, ) -> EnumArray: - """Encode a string numpy array, an enum item numpy array, or an int numpy - array into an :any:`EnumArray`. See :any:`EnumArray.decode` for - decoding. + """Encode an encodable array into an ``EnumArray``. - :param numpy.ndarray array: Array of string identifiers, or of enum - items, to encode. + Args: + array: Array to encode. - :returns: An :any:`EnumArray` encoding the input array values. - :rtype: :any:`EnumArray` + Returns: + EnumArray: An ``EnumArray`` with the encoded input values. For instance: From 91beee1221089b4917f16bd6d42364f2b5f8a7c2 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Sat, 5 Oct 2024 14:32:55 +0200 Subject: [PATCH 2/6] docs(enums): add docs to EnumArray (#1271) --- openfisca_core/indexed_enums/enum_array.py | 77 ++++++++++++++++++---- setup.cfg | 14 +++- 2 files changed, 78 insertions(+), 13 deletions(-) diff --git a/openfisca_core/indexed_enums/enum_array.py b/openfisca_core/indexed_enums/enum_array.py index a1479d5b8..dc63071da 100644 --- a/openfisca_core/indexed_enums/enum_array.py +++ b/openfisca_core/indexed_enums/enum_array.py @@ -9,41 +9,84 @@ class EnumArray(t.EnumArray): - """NumPy array subclass representing an array of enum items. + """Subclass of :any:`numpy.ndarray` representing an array of ``Enum``. + + ``Enum`` arrays are encoded as :any:`int` arrays to improve performance. + + Note: + Subclassing :any:`numpy.ndarray` is a little tricky™. To read more + about the :meth:`.__new__` and :meth:`.__array_finalize__` methods + below, see `Subclassing ndarray`_. + + .. _Subclassing ndarray: + https://numpy.org/doc/stable/user/basics.subclassing.html - EnumArrays are encoded as ``int`` arrays to improve performance """ - # Subclassing ndarray is a little tricky. - # To read more about the two following methods, see: - # https://docs.scipy.org/doc/numpy-1.13.0/user/basics.subclassing.html#slightly-more-realistic-example-attribute-added-to-existing-array. def __new__( cls, input_array: t.Array[t.DTypeEnum], possible_values: None | type[t.Enum] = None, ) -> Self: + """See comment above.""" obj = numpy.asarray(input_array).view(cls) obj.possible_values = possible_values return obj - # See previous comment def __array_finalize__(self, obj: numpy.int32 | None) -> None: + """See comment above.""" if obj is None: return self.possible_values = getattr(obj, "possible_values", None) def __eq__(self, other: object) -> bool: - # When comparing to an item of self.possible_values, use the item index - # to speed up the comparison. + """Compare equality with the item index. + + When comparing to an item of :attr:`.possible_values`, use the item + index to speed up the comparison. + + Whenever possible, use :any:`numpy.ndarray.view` so that the result is + a classic :any:`numpy.ndarray`, not an :obj:`.EnumArray`. + + Args: + other: Another object to compare to. + + Returns: + bool: When ??? + numpy.ndarray[numpy.bool_]: When ??? + + Note: + This breaks the `Liskov substitution principle`_. + + .. _Liskov substitution principle: + https://en.wikipedia.org/wiki/Liskov_substitution_principle + + """ + if other.__class__.__name__ is self.possible_values.__name__: - # Use view(ndarray) so that the result is a classic ndarray, not an - # EnumArray. return self.view(numpy.ndarray) == other.index return self.view(numpy.ndarray) == other def __ne__(self, other: object) -> bool: + """Inequality… + + Args: + other: Another object to compare to. + + Returns: + bool: When ??? + numpy.ndarray[numpy.bool_]: When ??? + + Note: + This breaks the `Liskov substitution principle`_. + + .. _Liskov substitution principle: + https://en.wikipedia.org/wiki/Liskov_substitution_principle + + """ + return numpy.logical_not(self == other) def _forbidden_operation(self, other: Any) -> NoReturn: @@ -65,7 +108,10 @@ def _forbidden_operation(self, other: Any) -> NoReturn: __or__ = _forbidden_operation def decode(self) -> numpy.object_: - """Return the array of enum items corresponding to self. + """Decode itself to a normal array. + + Returns: + numpy.ndarray[t.Enum]: The enum items of the ``EnumArray``. For instance: @@ -76,14 +122,19 @@ def decode(self) -> numpy.object_: Decoded value: enum item + """ + return numpy.select( [self == item.index for item in self.possible_values], list(self.possible_values), ) def decode_to_str(self) -> numpy.str_: - """Return the array of string identifiers corresponding to self. + """Decode itself to an array of strings. + + Returns: + numpy.ndarray[numpy.str_]: The string values of the ``EnumArray``. For instance: @@ -92,7 +143,9 @@ def decode_to_str(self) -> numpy.str_: >>> 2 # Encoded value >>> enum_array.decode_to_str()[0] 'free_lodger' # String identifier + """ + return numpy.select( [self == item.index for item in self.possible_values], [item.name for item in self.possible_values], diff --git a/setup.cfg b/setup.cfg index 9b8ce699b..fd18e5ab3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -11,7 +11,19 @@ convention = google docstring_style = google extend-ignore = D -ignore = B019, E203, E501, F405, E701, E704, RST212, RST213, RST301, RST306, W503 +ignore = + B019, + E203, + E501, + F405, + E701, + E704, + RST210, + RST212, + RST213, + RST301, + RST306, + W503 in-place = true include-in-doctest = openfisca_core/commons From 15c67c07c17af3a97676251b83d58b9c5ae1c69e Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Sat, 5 Oct 2024 15:53:14 +0200 Subject: [PATCH 3/6] docs(enums): fix sphinx markup (#1271) --- openfisca_core/indexed_enums/enum.py | 19 +++++++++------- openfisca_core/indexed_enums/enum_array.py | 25 ++++++++++++---------- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/openfisca_core/indexed_enums/enum.py b/openfisca_core/indexed_enums/enum.py index d76c9f9e2..a67cd317c 100644 --- a/openfisca_core/indexed_enums/enum.py +++ b/openfisca_core/indexed_enums/enum.py @@ -10,16 +10,16 @@ class Enum(t.Enum): """Enum based on `enum34 `_. - Its items have an :any:`int` index. This is useful and performant when - running simulations on large populations. + Its items have an :class:`int` index, useful and performant when running + :mod:`~openfisca_core.simulations` on large :mod:`~openfisca_core.populations`. """ - #: The ``index`` of the ``Enum`` member. + #: The :attr:`index` of the :class:`.Enum` member. index: int def __init__(self, *__args: object, **__kwargs: object) -> None: - """Tweak :any:`~enum.Enum` to add an index to each enum item. + """Tweak :class:`enum.Enum` to add an :attr:`.index` to each enum item. When the enum is initialised, ``_member_names_`` contains the names of the already initialized items, so its length is the index of this item. @@ -28,11 +28,14 @@ def __init__(self, *__args: object, **__kwargs: object) -> None: *__args: Positional arguments. **__kwargs: Keyword arguments. + Note: + ``_member_names_`` is undocumented in upstream :class:`enum.Enum`. + """ self.index = len(self._member_names_) - #: Bypass the slow :any:`~enum.Enum.__eq__` method. + #: Bypass the slow :meth:`enum.Enum.__eq__` method. __eq__ = object.__eq__ #: :meth:`.__hash__` must also be defined so as to stay hashable. @@ -43,13 +46,13 @@ def encode( cls, array: EnumArray | numpy.int32 | numpy.float32 | numpy.object_, ) -> EnumArray: - """Encode an encodable array into an ``EnumArray``. + """Encode an encodable array into an :class:`.EnumArray`. Args: - array: Array to encode. + array: :class:`~numpy.ndarray` to encode. Returns: - EnumArray: An ``EnumArray`` with the encoded input values. + EnumArray: An :class:`.EnumArray` with the encoded input values. For instance: diff --git a/openfisca_core/indexed_enums/enum_array.py b/openfisca_core/indexed_enums/enum_array.py index dc63071da..a04e28cdb 100644 --- a/openfisca_core/indexed_enums/enum_array.py +++ b/openfisca_core/indexed_enums/enum_array.py @@ -9,12 +9,12 @@ class EnumArray(t.EnumArray): - """Subclass of :any:`numpy.ndarray` representing an array of ``Enum``. + """A subclass of :class:`~numpy.ndarray` of :class:`.Enum`. - ``Enum`` arrays are encoded as :any:`int` arrays to improve performance. + :class:`.Enum` arrays are encoded as :class:`int` to improve performance. Note: - Subclassing :any:`numpy.ndarray` is a little tricky™. To read more + Subclassing :class:`~numpy.ndarray` is a little tricky™. To read more about the :meth:`.__new__` and :meth:`.__array_finalize__` methods below, see `Subclassing ndarray`_. @@ -23,6 +23,9 @@ class EnumArray(t.EnumArray): """ + #: Enum type of the array items. + possible_values: None | type[t.Enum] = None + def __new__( cls, input_array: t.Array[t.DTypeEnum], @@ -41,16 +44,16 @@ def __array_finalize__(self, obj: numpy.int32 | None) -> None: self.possible_values = getattr(obj, "possible_values", None) def __eq__(self, other: object) -> bool: - """Compare equality with the item index. + """Compare equality with the item's :attr:`~.Enum.index`. - When comparing to an item of :attr:`.possible_values`, use the item - index to speed up the comparison. + When comparing to an item of :attr:`.possible_values`, use the + item's :attr:`~.Enum.index`. to speed up the comparison. Whenever possible, use :any:`numpy.ndarray.view` so that the result is - a classic :any:`numpy.ndarray`, not an :obj:`.EnumArray`. + a classic :class:`~numpy.ndarray`, not an :obj:`.EnumArray`. Args: - other: Another object to compare to. + other: Another :class:`object` to compare to. Returns: bool: When ??? @@ -73,7 +76,7 @@ def __ne__(self, other: object) -> bool: """Inequality… Args: - other: Another object to compare to. + other: Another :class:`object` to compare to. Returns: bool: When ??? @@ -111,7 +114,7 @@ def decode(self) -> numpy.object_: """Decode itself to a normal array. Returns: - numpy.ndarray[t.Enum]: The enum items of the ``EnumArray``. + numpy.ndarray[t.Enum]: The items of the :obj:`.EnumArray`. For instance: @@ -134,7 +137,7 @@ def decode_to_str(self) -> numpy.str_: """Decode itself to an array of strings. Returns: - numpy.ndarray[numpy.str_]: The string values of the ``EnumArray``. + numpy.ndarray[numpy.str_]: The string values of the :obj:`.EnumArray`. For instance: From 6300df6286f2598af791fdb6ab206a70c7646263 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Sat, 5 Oct 2024 17:29:47 +0200 Subject: [PATCH 4/6] style(enums): fix linter issues (#1271) --- openfisca_core/indexed_enums/config.py | 3 +++ openfisca_core/indexed_enums/enum.py | 4 +++- openfisca_core/indexed_enums/enum_array.py | 12 ++++++------ setup.py | 2 +- stubs/numexpr/__init__.pyi | 1 + 5 files changed, 14 insertions(+), 8 deletions(-) diff --git a/openfisca_core/indexed_enums/config.py b/openfisca_core/indexed_enums/config.py index f7da69b84..abb8817de 100644 --- a/openfisca_core/indexed_enums/config.py +++ b/openfisca_core/indexed_enums/config.py @@ -1,3 +1,6 @@ import numpy ENUM_ARRAY_DTYPE = numpy.int16 + + +__all__ = ["ENUM_ARRAY_DTYPE"] diff --git a/openfisca_core/indexed_enums/enum.py b/openfisca_core/indexed_enums/enum.py index a67cd317c..56f007941 100644 --- a/openfisca_core/indexed_enums/enum.py +++ b/openfisca_core/indexed_enums/enum.py @@ -32,7 +32,6 @@ def __init__(self, *__args: object, **__kwargs: object) -> None: ``_member_names_`` is undocumented in upstream :class:`enum.Enum`. """ - self.index = len(self._member_names_) #: Bypass the slow :meth:`enum.Enum.__eq__` method. @@ -100,3 +99,6 @@ def encode( ).astype(ENUM_ARRAY_DTYPE) return EnumArray(array, cls) + + +__all__ = ["Enum"] diff --git a/openfisca_core/indexed_enums/enum_array.py b/openfisca_core/indexed_enums/enum_array.py index a04e28cdb..b40ba0401 100644 --- a/openfisca_core/indexed_enums/enum_array.py +++ b/openfisca_core/indexed_enums/enum_array.py @@ -66,14 +66,13 @@ def __eq__(self, other: object) -> bool: https://en.wikipedia.org/wiki/Liskov_substitution_principle """ - if other.__class__.__name__ is self.possible_values.__name__: return self.view(numpy.ndarray) == other.index return self.view(numpy.ndarray) == other def __ne__(self, other: object) -> bool: - """Inequality… + """Inequality. Args: other: Another :class:`object` to compare to. @@ -89,10 +88,10 @@ def __ne__(self, other: object) -> bool: https://en.wikipedia.org/wiki/Liskov_substitution_principle """ - return numpy.logical_not(self == other) - def _forbidden_operation(self, other: Any) -> NoReturn: + @staticmethod + def _forbidden_operation(other: Any) -> NoReturn: msg = ( "Forbidden operation. The only operations allowed on EnumArrays " "are '==' and '!='." @@ -127,7 +126,6 @@ def decode(self) -> numpy.object_: Decoded value: enum item """ - return numpy.select( [self == item.index for item in self.possible_values], list(self.possible_values), @@ -148,7 +146,6 @@ def decode_to_str(self) -> numpy.str_: 'free_lodger' # String identifier """ - return numpy.select( [self == item.index for item in self.possible_values], [item.name for item in self.possible_values], @@ -159,3 +156,6 @@ def __repr__(self) -> str: def __str__(self) -> str: return str(self.decode_to_str()) + + +__all__ = ["EnumArray"] diff --git a/setup.py b/setup.py index 202e5e449..d342bb9f4 100644 --- a/setup.py +++ b/setup.py @@ -62,7 +62,7 @@ "pylint >=3.3.1, <4.0", "pylint-per-file-ignores >=1.3.2, <2.0", "pyright >=1.1.382, <2.0", - "ruff >=0.6.7, <1.0", + "ruff >=0.6.9, <1.0", "ruff-lsp >=0.0.57, <1.0", "xdoctest >=1.2.0, <2.0", *api_requirements, diff --git a/stubs/numexpr/__init__.pyi b/stubs/numexpr/__init__.pyi index f9ada73c3..931d47ddb 100644 --- a/stubs/numexpr/__init__.pyi +++ b/stubs/numexpr/__init__.pyi @@ -4,6 +4,7 @@ import numpy def evaluate( __ex: str, + /, *__args: object, **__kwargs: object, ) -> NDArray[numpy.bool_] | NDArray[numpy.int32] | NDArray[numpy.float32]: ... From 8be6ae431666ef466daf31250a8b45262f3c7457 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Sat, 5 Oct 2024 18:53:07 +0200 Subject: [PATCH 5/6] style(sphinx): fix more doc markup errors (#1271) --- openfisca_core/commons/__init__.py | 53 +------------------ openfisca_core/commons/formulas.py | 14 ++--- openfisca_core/commons/misc.py | 8 ++- openfisca_core/commons/rates.py | 12 ++--- openfisca_core/commons/tests/test_dummy.py | 1 - openfisca_core/commons/tests/test_formulas.py | 7 --- openfisca_core/commons/tests/test_rates.py | 2 - openfisca_core/data_storage/__init__.py | 23 +------- .../data_storage/in_memory_storage.py | 25 ++++----- .../data_storage/on_disk_storage.py | 24 ++++----- openfisca_core/indexed_enums/__init__.py | 13 +---- openfisca_core/indexed_enums/enum_array.py | 8 +-- openfisca_tasks/lint.mk | 1 + 13 files changed, 40 insertions(+), 151 deletions(-) diff --git a/openfisca_core/commons/__init__.py b/openfisca_core/commons/__init__.py index 1a3d065ee..550088141 100644 --- a/openfisca_core/commons/__init__.py +++ b/openfisca_core/commons/__init__.py @@ -1,55 +1,4 @@ -"""Common tools for contributors and users. - -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:`.eval_expression` - * :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 - -""" +"""Common tools for contributors and users.""" from . import types from .dummy import Dummy diff --git a/openfisca_core/commons/formulas.py b/openfisca_core/commons/formulas.py index a184ad2dc..d83f187e2 100644 --- a/openfisca_core/commons/formulas.py +++ b/openfisca_core/commons/formulas.py @@ -24,10 +24,7 @@ def apply_thresholds( choices: A list of the possible values to choose from. Returns: - Array[numpy.float32]: A list of the values chosen. - - Raises: - AssertionError: When thresholds and choices are incompatible. + ndarray[float32]: A list of the values chosen. Examples: >>> input = numpy.array([4, 5, 6, 7, 8]) @@ -37,7 +34,6 @@ def apply_thresholds( array([10, 10, 15, 15, 20]) """ - condlist: list[t.Array[numpy.bool_] | bool] condlist = [input <= threshold for threshold in thresholds] @@ -66,7 +62,7 @@ def concat( that: Another array to concatenate. Returns: - Array[numpy.str_]: An array with the concatenated values. + ndarray[str_]: An array with the concatenated values. Examples: >>> this = ["this", "that"] @@ -75,7 +71,6 @@ def concat( array(['this1.0', 'that2.5']...) """ - if not isinstance(this, numpy.ndarray): this = numpy.array(this) @@ -105,10 +100,7 @@ def switch( value_by_condition: Values to replace for each condition. Returns: - Array: An array with the replaced values. - - Raises: - AssertionError: When ``value_by_condition`` is empty. + ndarray[float32]: An array with the replaced values. Examples: >>> conditions = numpy.array([1, 1, 1, 2]) diff --git a/openfisca_core/commons/misc.py b/openfisca_core/commons/misc.py index ba9687619..e3e55948d 100644 --- a/openfisca_core/commons/misc.py +++ b/openfisca_core/commons/misc.py @@ -13,7 +13,7 @@ def empty_clone(original: object) -> object: original: An object to clone. Returns: - The cloned, empty, object. + object: The cloned, empty, object. Examples: >>> Foo = type("Foo", (list,), {}) @@ -50,7 +50,7 @@ def stringify_array(array: None | t.Array[numpy.generic]) -> str: array: An array. Returns: - str: "None" if the ``array`` is None. + str: ``"None"`` if the ``array`` is ``None``. str: The stringified ``array`` otherwise. Examples: @@ -71,7 +71,6 @@ def stringify_array(array: None | t.Array[numpy.generic]) -> str: "[, {}, >> target = numpy.array([1, 2, 3]) @@ -37,7 +37,6 @@ def average_rate( array([ nan, 0. , -0.5]) """ - if not isinstance(varying, numpy.ndarray): varying = numpy.array(varying, dtype=numpy.float32) @@ -79,9 +78,9 @@ def marginal_rate( trim: The lower and upper bounds of the marginal rate. Returns: - Array[numpy.float32]: The marginal rate for each target. When ``trim`` - is provided, values that are out of the provided bounds are replaced by - :any:`numpy.nan`. + ndarray[float32]: The marginal rate for each target. When ``trim`` + is provided, values that are out of the provided bounds are + replaced by :class:`numpy.nan`. Examples: >>> target = numpy.array([1, 2, 3]) @@ -91,7 +90,6 @@ def marginal_rate( array([nan, 0.5]) """ - if not isinstance(varying, numpy.ndarray): varying = numpy.array(varying, dtype=numpy.float32) diff --git a/openfisca_core/commons/tests/test_dummy.py b/openfisca_core/commons/tests/test_dummy.py index dfe04b3e4..4dd13eaba 100644 --- a/openfisca_core/commons/tests/test_dummy.py +++ b/openfisca_core/commons/tests/test_dummy.py @@ -5,6 +5,5 @@ def test_dummy_deprecation() -> None: """Dummy throws a deprecation warning when instantiated.""" - with pytest.warns(DeprecationWarning): assert Dummy() diff --git a/openfisca_core/commons/tests/test_formulas.py b/openfisca_core/commons/tests/test_formulas.py index 130df9505..6fa98a7c2 100644 --- a/openfisca_core/commons/tests/test_formulas.py +++ b/openfisca_core/commons/tests/test_formulas.py @@ -7,7 +7,6 @@ def test_apply_thresholds_when_several_inputs() -> None: """Make 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] @@ -19,7 +18,6 @@ def test_apply_thresholds_when_several_inputs() -> None: def test_apply_thresholds_when_too_many_thresholds() -> None: """Raise an AssertionError when thresholds > choices.""" - input_ = numpy.array([6]) thresholds = [5, 7, 9, 11] choices = [10, 15, 20] @@ -30,7 +28,6 @@ def test_apply_thresholds_when_too_many_thresholds() -> None: def test_apply_thresholds_when_too_many_choices() -> None: """Raise an AssertionError when thresholds < choices - 1.""" - input_ = numpy.array([6]) thresholds = [5, 7] choices = [10, 15, 20, 25] @@ -41,7 +38,6 @@ def test_apply_thresholds_when_too_many_choices() -> None: def test_concat_when_this_is_array_not_str() -> None: """Cast ``this`` to ``str`` when it is a NumPy array other than string.""" - this = numpy.array([1, 2]) that = numpy.array(["la", "o"]) @@ -52,7 +48,6 @@ def test_concat_when_this_is_array_not_str() -> None: def test_concat_when_that_is_array_not_str() -> None: """Cast ``that`` to ``str`` when it is a NumPy array other than string.""" - this = numpy.array(["ho", "cha"]) that = numpy.array([1, 2]) @@ -63,7 +58,6 @@ def test_concat_when_that_is_array_not_str() -> None: def test_concat_when_args_not_str_array_like() -> None: """Cast ``this`` and ``that`` to a NumPy array or strings.""" - this = (1, 2) that = (3, 4) @@ -74,7 +68,6 @@ def test_concat_when_args_not_str_array_like() -> None: def test_switch_when_values_are_empty() -> None: """Raise an AssertionError when the values are empty.""" - conditions = [1, 1, 1, 2] value_by_condition = {} diff --git a/openfisca_core/commons/tests/test_rates.py b/openfisca_core/commons/tests/test_rates.py index c266582fc..fbee4cc83 100644 --- a/openfisca_core/commons/tests/test_rates.py +++ b/openfisca_core/commons/tests/test_rates.py @@ -8,7 +8,6 @@ def test_average_rate_when_varying_is_zero() -> None: """Yield infinity when the varying gross income crosses zero.""" - target = numpy.array([1, 2, 3]) varying = [0, 0, 0] @@ -19,7 +18,6 @@ def test_average_rate_when_varying_is_zero() -> None: def test_marginal_rate_when_varying_is_zero() -> None: """Yield infinity when the varying gross income crosses zero.""" - target = numpy.array([1, 2, 3]) varying = numpy.array([0, 0, 0]) diff --git a/openfisca_core/data_storage/__init__.py b/openfisca_core/data_storage/__init__.py index 9f63047fb..4dbbb8954 100644 --- a/openfisca_core/data_storage/__init__.py +++ b/openfisca_core/data_storage/__init__.py @@ -1,25 +1,4 @@ -# 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 +"""Different storage backends for the data of a simulation.""" from . import types from .in_memory_storage import InMemoryStorage diff --git a/openfisca_core/data_storage/in_memory_storage.py b/openfisca_core/data_storage/in_memory_storage.py index 18387ff64..cd8f9ef08 100644 --- a/openfisca_core/data_storage/in_memory_storage.py +++ b/openfisca_core/data_storage/in_memory_storage.py @@ -29,13 +29,15 @@ def __init__(self, is_eternal: bool = False) -> None: self.is_eternal = is_eternal def get(self, period: None | t.Period = None) -> None | t.Array[t.DTypeGeneric]: - """Retrieve the data for the specified period from memory. + """Retrieve the data for the specified :obj:`.Period` from memory. Args: - period: The period for which data should be retrieved. + period: The :obj:`.Period` for which data should be retrieved. Returns: - The data for the specified period, or None if no data is available. + None: If no data is available. + EnumArray: The data for the specified :obj:`.Period`. + ndarray[generic]: The data for the specified :obj:`.Period`. Examples: >>> import numpy @@ -53,7 +55,6 @@ def get(self, period: None | t.Period = None) -> None | t.Array[t.DTypeGeneric]: array([1, 2, 3]) """ - if self.is_eternal: period = periods.period(DateUnit.ETERNITY) period = periods.period(period) @@ -64,11 +65,11 @@ def get(self, period: None | t.Period = None) -> None | t.Array[t.DTypeGeneric]: return values def put(self, value: t.Array[t.DTypeGeneric], period: None | t.Period) -> None: - """Store the specified data in memory for the specified period. + """Store the specified data in memory for the specified :obj:`.Period`. Args: value: The data to store - period: The period for which the data should be stored. + period: The :obj:`.Period` for which the data should be stored. Examples: >>> import numpy @@ -86,7 +87,6 @@ def put(self, value: t.Array[t.DTypeGeneric], period: None | t.Period) -> None: array(['1', '2', 'salary'], dtype=' None: self._arrays[period] = value def delete(self, period: None | t.Period = None) -> None: - """Delete the data for the specified period from memory. + """Delete the data for the specified :obj:`.Period` from memory. Args: - period: The period for which data should be deleted. + period: The :obj:`.Period` for which data should be deleted. Note: If ``period`` is specified, all data will be deleted. @@ -128,7 +128,6 @@ def delete(self, period: None | t.Period = None) -> None: >>> storage.get(period) """ - if period is None: self._arrays = {} return @@ -147,7 +146,7 @@ def get_known_periods(self) -> KeysView[t.Period]: """List of storage's known periods. Returns: - A sequence containing the storage's known periods. + KeysView[Period]: A sequence containing the storage's known periods. Examples: >>> from openfisca_core import data_storage, periods @@ -164,14 +163,13 @@ def get_known_periods(self) -> KeysView[t.Period]: dict_keys([Period(('year', Instant((2017, 1, 1)), 1))]) """ - return self._arrays.keys() def get_memory_usage(self) -> t.MemoryUsage: """Memory usage of the storage. Returns: - A dictionary representing the storage's memory usage. + MemoryUsage: A dictionary representing the storage's memory usage. Examples: >>> from openfisca_core import data_storage @@ -181,7 +179,6 @@ def get_memory_usage(self) -> t.MemoryUsage: {'nb_arrays': 0, 'total_nb_bytes': 0, 'cell_size': nan} """ - if not self._arrays: return { "nb_arrays": 0, diff --git a/openfisca_core/data_storage/on_disk_storage.py b/openfisca_core/data_storage/on_disk_storage.py index d1b8e2c4e..3d0ef7fc1 100644 --- a/openfisca_core/data_storage/on_disk_storage.py +++ b/openfisca_core/data_storage/on_disk_storage.py @@ -33,7 +33,7 @@ class OnDiskStorage: #: Whether to preserve the storage directory. preserve_storage_dir: bool - #: Mapping of file paths to possible Enum values. + #: Mapping of file paths to possible :class:`.Enum` values. _enums: MutableMapping[str, type[t.Enum]] #: Mapping of periods to file paths. @@ -52,17 +52,18 @@ def __init__( self.storage_dir = storage_dir def _decode_file(self, file: str) -> t.Array[t.DTypeGeneric]: - """Decode a file by loading its contents as a ``numpy`` array. + """Decode a file by loading its contents as a :mod:`numpy` array. Args: file: Path to the file to be decoded. Returns: - ``numpy`` array or ``EnumArray`` representing the data in the file. + EnumArray: Representing the data in the file. + ndarray[generic]: Representing the data in the file. Note: - If the file is associated with ``Enum`` values, the array is - converted back to an ``EnumArray`` object. + If the file is associated with :class:`~indexed_enums.Enum` values, the + array is converted back to an :obj:`~indexed_enums.EnumArray` object. Examples: >>> import tempfile @@ -89,7 +90,6 @@ def _decode_file(self, file: str) -> t.Array[t.DTypeGeneric]: EnumArray([]) """ - enum = self._enums.get(file) if enum is not None: @@ -106,8 +106,9 @@ def get(self, period: None | t.Period = None) -> None | t.Array[t.DTypeGeneric]: period: The period for which data should be retrieved. Returns: - A ``numpy`` array or ``EnumArray`` representing the vector for the - specified period, or ``None`` if no vector is stored. + None: If no data is available. + EnumArray: Representing the data for the specified period. + ndarray[generic]: Representing the data for the specified period. Examples: >>> import tempfile @@ -127,7 +128,6 @@ def get(self, period: None | t.Period = None) -> None | t.Array[t.DTypeGeneric]: array([1, 2, 3]) """ - if self.is_eternal: period = periods.period(DateUnit.ETERNITY) period = periods.period(period) @@ -162,7 +162,6 @@ def put(self, value: t.Array[t.DTypeGeneric], period: None | t.Period) -> None: array(['1', '2', 'salary'], dtype=' None: ... storage.get(period) """ - if period is None: self._files = {} return @@ -231,7 +229,7 @@ def get_known_periods(self) -> KeysView[t.Period]: """List of storage's known periods. Returns: - A sequence containing the storage's known periods. + KeysView[Period]: A sequence containing the storage's known periods. Examples: >>> import tempfile @@ -255,7 +253,6 @@ def get_known_periods(self) -> KeysView[t.Period]: dict_keys([Period(('year', Instant((2017, 1, 1)), 1))]) """ - return self._files.keys() def restore(self) -> None: @@ -289,7 +286,6 @@ def restore(self) -> None: >>> directory.cleanup() """ - self._files = files = {} # Restore self._files from content of storage_dir. for filename in os.listdir(self.storage_dir): diff --git a/openfisca_core/indexed_enums/__init__.py b/openfisca_core/indexed_enums/__init__.py index 70e2d3549..a6a452511 100644 --- a/openfisca_core/indexed_enums/__init__.py +++ b/openfisca_core/indexed_enums/__init__.py @@ -1,15 +1,4 @@ -"""Enumerations for variables with a limited set of possible values. - -These include: - * Highest academic level: high school, associate degree, bachelor's degree, - master's degree, doctorate… - * A household housing occupancy status: owner, tenant, free-lodger, - homeless… - * The main occupation of a person: employee, freelancer, retired, student, - unemployed… - * Etc. - -""" +"""Enumerations for variables with a limited set of possible values.""" from . import types from .config import ENUM_ARRAY_DTYPE diff --git a/openfisca_core/indexed_enums/enum_array.py b/openfisca_core/indexed_enums/enum_array.py index b40ba0401..93c848698 100644 --- a/openfisca_core/indexed_enums/enum_array.py +++ b/openfisca_core/indexed_enums/enum_array.py @@ -57,7 +57,7 @@ def __eq__(self, other: object) -> bool: Returns: bool: When ??? - numpy.ndarray[numpy.bool_]: When ??? + ndarray[bool_]: When ??? Note: This breaks the `Liskov substitution principle`_. @@ -79,7 +79,7 @@ def __ne__(self, other: object) -> bool: Returns: bool: When ??? - numpy.ndarray[numpy.bool_]: When ??? + ndarray[bool_]: When ??? Note: This breaks the `Liskov substitution principle`_. @@ -113,7 +113,7 @@ def decode(self) -> numpy.object_: """Decode itself to a normal array. Returns: - numpy.ndarray[t.Enum]: The items of the :obj:`.EnumArray`. + ndarray[Enum]: The items of the :obj:`.EnumArray`. For instance: @@ -135,7 +135,7 @@ def decode_to_str(self) -> numpy.str_: """Decode itself to an array of strings. Returns: - numpy.ndarray[numpy.str_]: The string values of the :obj:`.EnumArray`. + ndarray[str_]: The string values of the :obj:`.EnumArray`. For instance: diff --git a/openfisca_tasks/lint.mk b/openfisca_tasks/lint.mk index a3f5a8e45..f5fdbc7ce 100644 --- a/openfisca_tasks/lint.mk +++ b/openfisca_tasks/lint.mk @@ -21,6 +21,7 @@ lint-doc: \ lint-doc-commons \ lint-doc-data_storage \ lint-doc-entities \ + lint-doc-indexed_enums \ ; ## Run linters to check for syntax and style errors in the doc. From 6783cb6ec7d50da2f51b17097a57b90f247cda5c Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Sat, 5 Oct 2024 18:56:16 +0200 Subject: [PATCH 6/6] chore: version bump (fixes #1271) --- CHANGELOG.md | 6 ++++++ setup.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f900c330..74f86b175 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +### 42.0.8 [#1272](https://github.com/openfisca/openfisca-core/pull/1272) + +#### Documentation + +- Add documentation to the `indexed_enums` module + ### 42.0.7 [#1264](https://github.com/openfisca/openfisca-core/pull/1264) #### Technical changes diff --git a/setup.py b/setup.py index d342bb9f4..491479ccb 100644 --- a/setup.py +++ b/setup.py @@ -70,7 +70,7 @@ setup( name="OpenFisca-Core", - version="42.0.7", + version="42.0.8", author="OpenFisca Team", author_email="contact@openfisca.org", classifiers=[