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: encapsulate periods module #1138

Draft
wants to merge 94 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 40 commits
Commits
Show all changes
94 commits
Select commit Hold shift + click to select a range
9872a39
Fix flake8/pycodestyle dependency error
bonjourmauko Jul 31, 2022
35e02c5
Add periods to doctests path
bonjourmauko Jul 28, 2022
8ba7fe1
Ignore mypy periods' tests errors
bonjourmauko Jul 28, 2022
496951f
Fix instants' docstrings
bonjourmauko Jul 28, 2022
fbafc60
Fix periods' doctests
bonjourmauko Jul 28, 2022
1b8cfec
Fix periods' helpers doctests
bonjourmauko Jul 28, 2022
09fab88
Add tests to periods.instant
bonjourmauko Jul 30, 2022
2ecbdb8
Add tests to periods.instant_date
bonjourmauko Jul 30, 2022
0e51ac5
Add tests to periods.key_period_size
bonjourmauko Jul 30, 2022
a7e9851
Add doctest to periods.unit_weight
bonjourmauko Jul 30, 2022
aff6d23
Extract parse simple period from builder
bonjourmauko Jul 30, 2022
039468d
Add tests to periods.period
bonjourmauko Jul 30, 2022
85717fa
Refactor period's str year tests
bonjourmauko Jul 30, 2022
3fc64b6
Refactor period's str month tests
bonjourmauko Jul 30, 2022
7503d27
Refactor period's str day tests
bonjourmauko Jul 30, 2022
fed7c4a
Remove redundant examples
bonjourmauko Jul 30, 2022
bcd1edc
Redistribute tests
bonjourmauko Jul 30, 2022
309d58e
Consolidate tests
bonjourmauko Jul 30, 2022
fb6c119
Revert "Fix flake8/pycodestyle dependency error"
bonjourmauko Aug 1, 2022
5256174
Rationalise Instant doc & tests
bonjourmauko Aug 2, 2022
632566a
Simplify periods' doc
bonjourmauko Aug 2, 2022
45b1eb2
Rationalise period's offset tests
bonjourmauko Aug 2, 2022
01c1cd0
Version & CHANGELOG bump
bonjourmauko Jul 28, 2022
c5b2b1f
Reorder package
bonjourmauko Dec 14, 2022
f2e8085
Fix style
bonjourmauko Dec 14, 2022
35b03db
Rename zinstant` to `build_instant`
bonjourmauko Dec 14, 2022
746d29c
Rename to
bonjourmauko Dec 14, 2022
03ce49b
Move to
bonjourmauko Dec 14, 2022
4afec7a
Do not expose Instant cache
bonjourmauko Dec 14, 2022
4a3b32d
Fix documentation
bonjourmauko Dec 14, 2022
3c667e3
Fix circular dependencies
bonjourmauko Dec 14, 2022
6eb90a2
Make stricter
bonjourmauko Dec 14, 2022
31c2d32
Refactor _parse_unit
bonjourmauko Dec 14, 2022
9a96aed
Refactor as
bonjourmauko Dec 14, 2022
1b79d3c
Fix typing
bonjourmauko Dec 14, 2022
15a8678
Fix typing in py3.7
bonjourmauko Dec 14, 2022
c56c2a2
Rename period & instant
bonjourmauko Dec 14, 2022
51e2312
Fix imports
bonjourmauko Dec 14, 2022
baad937
Encapsulate periods
bonjourmauko Dec 14, 2022
7a11ece
Simplify dict
bonjourmauko Dec 14, 2022
69126fa
Remove assertion in periods.helpers
bonjourmauko Dec 14, 2022
996d987
Avoid inf to respect period type
bonjourmauko Dec 14, 2022
f34b6a6
Refactor offset
bonjourmauko Dec 14, 2022
923c8a0
Deprecate instant to date
bonjourmauko Dec 14, 2022
66224e5
Rename to
bonjourmauko Dec 14, 2022
3b3c27a
Fix typing
bonjourmauko Dec 15, 2022
ed844c8
Add isort config
bonjourmauko Dec 16, 2022
51d3699
Rename build_instant to Instant.build
bonjourmauko Dec 16, 2022
885985d
Fix doctests in Instant
bonjourmauko Dec 16, 2022
3bcaf71
Refactor the Period.stop function
bonjourmauko Dec 17, 2022
ae23f09
Refactor size_in_x
bonjourmauko Dec 17, 2022
2f4503e
Fix tests
bonjourmauko Dec 17, 2022
77c86ba
Adapt codebase to changes in Instant
bonjourmauko Dec 17, 2022
1ee0837
Refactor unit weights
bonjourmauko Dec 17, 2022
8275a59
Deprecate key_value_period
bonjourmauko Dec 17, 2022
d465f54
Refactor date units
bonjourmauko Dec 17, 2022
fd648d4
Fix remaining tests
bonjourmauko Dec 17, 2022
06d0c24
Replace the parsing function with the parser
bonjourmauko Dec 17, 2022
1211c76
Move the builder back into Period
bonjourmauko Dec 17, 2022
5479775
Replace build_period by Period.build
bonjourmauko Dec 17, 2022
7adcac6
Adapt public exposed API
bonjourmauko Dec 17, 2022
0f365de
Fix documentation
bonjourmauko Dec 17, 2022
b99c899
Fix DateUnit types
bonjourmauko Dec 17, 2022
2e05c76
Fix period types
bonjourmauko Dec 17, 2022
b45233d
Fix tracer types
bonjourmauko Dec 17, 2022
24d0e84
Fix variable types
bonjourmauko Dec 17, 2022
2922ea6
Fix Instant.add types
bonjourmauko Dec 17, 2022
d165525
Refactor into Add protocol
bonjourmauko Dec 17, 2022
372c0e9
Remove black magic from instant -> period
bonjourmauko Dec 17, 2022
76f8863
Remove unused types
bonjourmauko Dec 17, 2022
129d701
Improve readability of tests
bonjourmauko Dec 18, 2022
fc644f7
Use named isoformat attrs
bonjourmauko Dec 18, 2022
cbcceab
Simplify iso parser
bonjourmauko Dec 18, 2022
f84afbf
Fix type error in instant
bonjourmauko Dec 18, 2022
97de3ee
Update CHANGELOG
bonjourmauko Dec 18, 2022
2fc824e
Cleanup types
bonjourmauko Dec 18, 2022
499d894
Add more examples to DateUnit
bonjourmauko Dec 18, 2022
6addf76
Remove unused exception
bonjourmauko Dec 19, 2022
f755138
Make simpler test execution
bonjourmauko Dec 19, 2022
574973c
Get rid of last type stub
bonjourmauko Dec 19, 2022
a6c00c9
Minimise changes to the public API
bonjourmauko Dec 19, 2022
dd523b6
Reuse parsers to simplify period build
bonjourmauko Dec 19, 2022
bae8d52
Merge branch 'master' into doctests/doctests-standalone
bonjourmauko Dec 19, 2022
091fe10
Improved period factory
bonjourmauko Dec 19, 2022
7e1d3b3
Improve packaging
bonjourmauko Dec 19, 2022
23facd0
Keep file names
bonjourmauko Dec 19, 2022
d82f78f
Add the offsetable protocol
bonjourmauko Dec 22, 2022
aac33e1
Make Instant more anglo-saxpon friendly
bonjourmauko Dec 22, 2022
7e17e58
Fix period implementation
bonjourmauko Dec 22, 2022
574cf5f
Fix offsetable type
bonjourmauko Dec 22, 2022
4c168df
Make Instant._add private
bonjourmauko Dec 23, 2022
d3b24df
Refactor Instant.offset
bonjourmauko Dec 23, 2022
30e4164
Make Period.__str__ less surprising
bonjourmauko Dec 23, 2022
56daa66
Harmonise offset syntax
bonjourmauko Dec 23, 2022
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
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
# Changelog

# 39.0.0 [#1138](https://github.com/openfisca/openfisca-core/pull/1138)

#### Breaking changes

- Deprecate `periods.intersect`.
- Deprecate `periods.unit_weight`.
- Deprecate `periods.unit_weights`.
- Rename `instant` to `build_instant`.
- Rename `period` to `build_period`.
- Move `instant_date` to `Instant.to_date`.
- Refactor `Period.contains` as `Period.__contains__`.
- Make `periods.parse_period` stricter (for example `2022-1` now fails).

#### Technical changes

- Add typing to `openfisca_core.periods`.
- Fix `openfisca_core.periods` doctests.
- Document `openfisca_core.periods`.

# 38.0.0 [#989](https://github.com/openfisca/openfisca-core/pull/989)

#### New Features
Expand Down
14 changes: 7 additions & 7 deletions openfisca_core/data_storage/in_memory_storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ def __init__(self, is_eternal = False):

def get(self, period):
if self.is_eternal:
period = periods.period(periods.ETERNITY)
period = periods.period(period)
period = periods.build_period(periods.ETERNITY)
period = periods.build_period(period)

values = self._arrays.get(period)
if values is None:
Expand All @@ -24,8 +24,8 @@ def get(self, period):

def put(self, value, period):
if self.is_eternal:
period = periods.period(periods.ETERNITY)
period = periods.period(period)
period = periods.build_period(periods.ETERNITY)
period = periods.build_period(period)

self._arrays[period] = value

Expand All @@ -35,13 +35,13 @@ def delete(self, period = None):
return

if self.is_eternal:
period = periods.period(periods.ETERNITY)
period = periods.period(period)
period = periods.build_period(periods.ETERNITY)
period = periods.build_period(period)

self._arrays = {
period_item: value
for period_item, value in self._arrays.items()
if not period.contains(period_item)
if period_item not in period
}

def get_known_periods(self):
Expand Down
16 changes: 8 additions & 8 deletions openfisca_core/data_storage/on_disk_storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ def _decode_file(self, file):

def get(self, period):
if self.is_eternal:
period = periods.period(periods.ETERNITY)
period = periods.period(period)
period = periods.build_period(periods.ETERNITY)
period = periods.build_period(period)

values = self._files.get(period)
if values is None:
Expand All @@ -38,8 +38,8 @@ def get(self, period):

def put(self, value, period):
if self.is_eternal:
period = periods.period(periods.ETERNITY)
period = periods.period(period)
period = periods.build_period(periods.ETERNITY)
period = periods.build_period(period)

filename = str(period)
path = os.path.join(self.storage_dir, filename) + '.npy'
Expand All @@ -55,14 +55,14 @@ def delete(self, period = None):
return

if self.is_eternal:
period = periods.period(periods.ETERNITY)
period = periods.period(period)
period = periods.build_period(periods.ETERNITY)
period = periods.build_period(period)

if period is not None:
self._files = {
period_item: value
for period_item, value in self._files.items()
if not period.contains(period_item)
if period_item not in period
}

def get_known_periods(self):
Expand All @@ -76,7 +76,7 @@ def restore(self):
continue
path = os.path.join(self.storage_dir, filename)
filename_core = filename.rsplit('.', 1)[0]
period = periods.period(filename_core)
period = periods.build_period(filename_core)
files[period] = path

def __del__(self):
Expand Down
23 changes: 12 additions & 11 deletions openfisca_core/holders/holder.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,12 @@
import numpy
import psutil

from openfisca_core import (
errors,
commons,
data_storage as storage,
indexed_enums as enums,
periods,
tools,
types,
)
from openfisca_core import commons
from openfisca_core import data_storage as storage
from openfisca_core import errors
from openfisca_core import indexed_enums as enums
from openfisca_core import periods, tools
from openfisca_core.periods.typing import Period

from .memory_usage import MemoryUsage

Expand Down Expand Up @@ -164,7 +161,7 @@ def get_known_periods(self):

def set_input(
self,
period: types.Period,
period: Period,
array: Union[numpy.ndarray, Sequence[Any]],
) -> Optional[numpy.ndarray]:
"""Set a Variable's array of values of a given Period.
Expand Down Expand Up @@ -210,7 +207,11 @@ def set_input(

"""

period = periods.period(period)
period = periods.build_period(period)

if period is None:
raise ValueError(f"Invalid period value: {period}")

if period.unit == periods.ETERNITY and self.variable.definition_period != periods.ETERNITY:
error_message = os.linesep.join([
'Unable to set a value for variable {0} for periods.ETERNITY.',
Expand Down
2 changes: 1 addition & 1 deletion openfisca_core/model_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
ValuesHistory,
)

from openfisca_core.periods import DAY, MONTH, YEAR, ETERNITY, period # noqa: F401
from openfisca_core.periods import DAY, MONTH, YEAR, ETERNITY, build_period # noqa: F401
from openfisca_core.populations import ADD, DIVIDE # noqa: F401
from openfisca_core.reforms import Reform # noqa: F401

Expand Down
2 changes: 1 addition & 1 deletion openfisca_core/parameters/at_instant_like.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def __call__(self, instant):
return self.get_at_instant(instant)

def get_at_instant(self, instant):
instant = str(periods.instant(instant))
instant = str(periods.build_instant(instant))
return self._get_at_instant(instant)

@abc.abstractmethod
Expand Down
2 changes: 1 addition & 1 deletion openfisca_core/parameters/parameter.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ def update(self, period = None, start = None, stop = None, value = None):
if start is not None or stop is not None:
raise TypeError("Wrong input for 'update' method: use either 'update(period, value = value)' or 'update(start = start, stop = stop, value = value)'. You cannot both use 'period' and 'start' or 'stop'.")
if isinstance(period, str):
period = periods.period(period)
period = periods.build_period(period)
start = period.start
stop = period.stop
if start is None:
Expand Down
79 changes: 34 additions & 45 deletions openfisca_core/periods/__init__.py
Original file line number Diff line number Diff line change
@@ -1,45 +1,34 @@
# 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

from .config import ( # noqa: F401
DAY,
MONTH,
YEAR,
ETERNITY,
INSTANT_PATTERN,
date_by_instant_cache,
str_by_instant_cache,
year_or_month_or_day_re,
)

from .helpers import ( # noqa: F401
instant,
instant_date,
period,
key_period_size,
unit_weights,
unit_weight,
)

from .instant_ import Instant # noqa: F401
from .period_ import Period # noqa: F401
"""Transitional imports to ensure non-breaking changes.

These imports could be deprecated in the next major release.

Currently, imports are used in the following way::
from openfisca_core.module import symbol

This example causes cyclic dependency problems, which prevent us from
modularising the different components of the library and make them easier to
test and maintain.

After the next major release, imports could be used in the following way::
from openfisca_core import module
module.symbol()

And for classes::
from openfisca_core.module import Symbol
Symbol()

.. 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

"""

from ._config import INSTANT_PATTERN
from ._funcs import build_instant, build_period, key_period_size, parse_period
from ._units import DAY, ETERNITY, MONTH, UNIT_WEIGHTS, YEAR
from .instant import Instant
from .period import Period
9 changes: 9 additions & 0 deletions openfisca_core/periods/_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from __future__ import annotations

from typing import Pattern

import re

# Matches "2015", "2015-01", "2015-01-01"
# Does not match "2015-13", "2015-12-32"
INSTANT_PATTERN: Pattern[str] = re.compile(r"^\d{4}(-(0[1-9]|1[012]))?(-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01]))?$")
62 changes: 62 additions & 0 deletions openfisca_core/periods/_errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
from __future__ import annotations

from typing import Any

from ._units import DAY, MONTH, YEAR
from .typing import Instant

LEARN_MORE = (
"Learn more about legal period formats in OpenFisca: "
"<https://openfisca.org/doc/coding-the-legislation/35_html#periods-in-simulations>."
)


class DateUnitValueError(ValueError):
"""Raised when a date unit's value is not valid."""

def __init__(self, value: Any) -> None:
super().__init__(
f"'{value}' is not a valid ISO format date unit. ISO format date "
f"units are any of: '{DAY}', '{MONTH}', or '{YEAR}'. {LEARN_MORE}"
)


class InstantFormatError(ValueError):
"""Raised when an instant's format is not valid (ISO format)."""

def __init__(self, value: Any) -> None:
super().__init__(
f"'{value}' is not a valid instant. Instants are described using "
"the 'YYYY-MM-DD' format, for instance '2015-06-15'. {LEARN_MORE}"
)


class InstantTypeError(TypeError):
"""Raised when an instant's type is not valid."""

def __init__(self, value: Any) -> None:
super().__init__(
f"Invalid instant: {value} of type {type(value)}, expecting an "
f"{type(Instant)}. {LEARN_MORE}"
)


class PeriodFormatError(ValueError):
"""Raised when a period's format is not valid ."""

def __init__(self, value: Any) -> None:
super().__init__(
f"'{value}' is not a valid period. Periods are described using "
"the 'unit:YYYY-MM-DD:size' format, for instance "
f"'day:2023-01-15:3'. {LEARN_MORE}"
)


class OffsetTypeError(TypeError):
"""Raised when an offset's type is not valid."""

def __init__(self, value: Any) -> None:
super().__init__(
f"Invalid offset: {value} of type {type(value)}, expecting an "
f"{type(int)}. {LEARN_MORE}"
)
Loading