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 all 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
59 changes: 58 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,62 @@
# Changelog

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

#### Breaking changes

##### Renames

- Rename `periods.period.get_subperiods` to `periods.period.subperiods`.

##### Deprecations

- Deprecate `INSTANT_PATTERN`
- The feature is now provided by `periods.parse`
- Deprecate `instant_date`.
- The feature is now provided by `periods.instant.date`.
- Deprecate `periods.{unit_weight, unit_weights, key_period_size}`.
- These features are now provided by `periods.dateunit`.
- Deprecate `periods.intersect`.
- The feature has no replacement.
- Make `periods.parse_period` stricter.
- For example `2022-1` now fails.
- Refactor `periods.period.contains` as `__contains__`.
- For example `subperiod in period` is now possible.

##### Structural changes

- Transform `Period.date` from property to method.
- Now it has to be used as `period.date()` (note the parenthesis).
- Transform `Instant.date` from property to method.
- Now it has to be used as `instant.date()` (note the parenthesis).
- Rationalise the reference periods.
- Before, there was a definite list of reference periods. For example,
`period.first_month` or `period.n_2`.
- This has been simplified to allow users to build their own:
- `period.ago(unit: DateUnit, size: int = 1) -> Period`.
- `period.last(unit: DateUnit, size: int = 1) -> Period`.
- `period.first(unit: DateUnit) -> Period`.
- Rationalise date units.
- Before, usage of "month", YEAR, and so on was fairly inconsistent, and
providing a perfect hotbed for bugs to breed.
- This has been fixed by introducing a new `dateunit` module, which
provides a single source of truth for all date units.
- Note that if you used `periods.YEAR` and the like, there is nothing to
change in your code.
- However, strings like `"year"` or `"ETERNITY"` are no longer allowed (in
fact, date unit are int enums an no longer strings).

#### Technical changes

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

#### Bug fixes

- Fixes incoherent dates.
- Fixes several race conditions.

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

#### New Features
Expand Down Expand Up @@ -35,7 +92,7 @@

#### Migration details

- Replace `some_period.start.period` and similar methods with `Period((unit, some_period.start, 1))`.
- Replace `some_period.start.period` and similar methods with `Period(unit, some_period.start, 1)`.

# 36.0.0 [#1149](https://github.com/openfisca/openfisca-core/pull/1162)

Expand Down
2 changes: 1 addition & 1 deletion openfisca_core/data_storage/in_memory_storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def delete(self, period = None):
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
2 changes: 1 addition & 1 deletion openfisca_core/data_storage/on_disk_storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def delete(self, period = 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 Down
7 changes: 3 additions & 4 deletions openfisca_core/holders/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import numpy

from openfisca_core import periods
from openfisca_core.periods import Period

log = logging.getLogger(__name__)

Expand All @@ -28,7 +27,7 @@ def set_input_dispatch_by_period(holder, period, array):
after_instant = period.start.offset(period_size, period_unit)

# Cache the input data, skipping the existing cached months
sub_period = Period((cached_period_unit, period.start, 1))
sub_period = periods.Period(cached_period_unit, period.start, 1)
while sub_period.start < after_instant:
existing_array = holder.get_array(sub_period)
if existing_array is None:
Expand Down Expand Up @@ -61,7 +60,7 @@ def set_input_divide_by_period(holder, period, array):

# Count the number of elementary periods to change, and the difference with what is already known.
remaining_array = array.copy()
sub_period = Period((cached_period_unit, period.start, 1))
sub_period = periods.Period(cached_period_unit, period.start, 1)
sub_periods_count = 0
while sub_period.start < after_instant:
existing_array = holder.get_array(sub_period)
Expand All @@ -74,7 +73,7 @@ def set_input_divide_by_period(holder, period, array):
# Cache the input data
if sub_periods_count > 0:
divided_array = remaining_array / sub_periods_count
sub_period = Period((cached_period_unit, period.start, 1))
sub_period = periods.Period(cached_period_unit, period.start, 1)
while sub_period.start < after_instant:
if holder.get_array(sub_period) is None:
holder._set(sub_period, divided_array)
Expand Down
36 changes: 18 additions & 18 deletions openfisca_core/holders/holder.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,18 @@
from __future__ import annotations

from typing import Any, Optional, Sequence, Union
from typing import Any, Sequence

import os
import warnings

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 .memory_usage import MemoryUsage

Expand Down Expand Up @@ -116,7 +112,7 @@ def get_memory_usage(self) -> MemoryUsage:
>>> entity = entities.Entity("", "", "", "")

>>> class MyVariable(variables.Variable):
... definition_period = "year"
... definition_period = periods.YEAR
... entity = entity
... value_type = int

Expand Down Expand Up @@ -164,9 +160,9 @@ def get_known_periods(self):

def set_input(
self,
period: types.Period,
array: Union[numpy.ndarray, Sequence[Any]],
) -> Optional[numpy.ndarray]:
period: periods.Period,
array: numpy.ndarray | Sequence[Any],
) -> numpy.ndarray | None:
"""Set a Variable's array of values of a given Period.

Args:
Expand All @@ -187,7 +183,7 @@ def set_input(
>>> entity = entities.Entity("", "", "", "")

>>> class MyVariable(variables.Variable):
... definition_period = "year"
... definition_period = periods.YEAR
... entity = entity
... value_type = int

Expand All @@ -211,13 +207,17 @@ def set_input(
"""

period = periods.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.',
'{0} is only defined for {1}s. Please adapt your input.',
]).format(
self.variable.name,
self.variable.definition_period
str(self.variable.definition_period)
)
raise errors.PeriodMismatchError(
self.variable.name,
Expand Down Expand Up @@ -265,10 +265,10 @@ def _set(self, period, value):
raise ValueError('A period must be specified to set values, except for variables with periods.ETERNITY as as period_definition.')
if (self.variable.definition_period != period.unit or period.size > 1):
name = self.variable.name
period_size_adj = f'{period.unit}' if (period.size == 1) else f'{period.size}-{period.unit}s'
period_size_adj = f'{str(period.unit)}' if (period.size == 1) else f'{period.size}-{str(period.unit)}s'
error_message = os.linesep.join([
f'Unable to set a value for variable "{name}" for {period_size_adj}-long period "{period}".',
f'"{name}" can only be set for one {self.variable.definition_period} at a time. Please adapt your input.',
f'"{name}" can only be set for one {str(self.variable.definition_period)} at a time. Please adapt your input.',
f'If you are the maintainer of "{name}", you can consider adding it a set_input attribute to enable automatic period casting.'
])

Expand Down
9 changes: 4 additions & 5 deletions openfisca_core/holders/tests/test_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from openfisca_core import holders, periods, tools
from openfisca_core.entities import Entity
from openfisca_core.holders import Holder
from openfisca_core.periods import Instant, Period
from openfisca_core.populations import Population
from openfisca_core.variables import Variable

Expand Down Expand Up @@ -58,8 +57,8 @@ def test_set_input_dispatch_by_period(
Income.definition_period = definition_unit
income = Income()
holder = Holder(income, population)
instant = Instant((2022, 1, 1))
dispatch_period = Period((dispatch_unit, instant, 3))
instant = periods.Instant(2022, 1, 1)
dispatch_period = periods.Period(dispatch_unit, instant, 3)

holders.set_input_dispatch_by_period(holder, dispatch_period, values)
total = sum(map(holder.get_array, holder.get_known_periods()))
Expand Down Expand Up @@ -89,8 +88,8 @@ def test_set_input_divide_by_period(
Income.definition_period = definition_unit
income = Income()
holder = Holder(income, population)
instant = Instant((2022, 1, 1))
divide_period = Period((divide_unit, instant, 3))
instant = periods.Instant(2022, 1, 1)
divide_period = periods.Period(divide_unit, instant, 3)

holders.set_input_divide_by_period(holder, divide_period, values)
last = holder.get_array(holder.get_known_periods()[-1])
Expand Down
31 changes: 13 additions & 18 deletions openfisca_core/model_api.py
Original file line number Diff line number Diff line change
@@ -1,39 +1,34 @@
from datetime import date # noqa: F401

from numpy import ( # noqa: F401
logical_not as not_,
maximum as max_,
minimum as min_,
round as round_,
select,
where,
from numpy import logical_not as not_ # noqa: F401
from numpy import maximum as max_ # noqa: F401
from numpy import minimum as min_ # noqa: F401
from numpy import round as round_ # noqa: F401
from numpy import select, where # noqa: F401

from openfisca_core import periods # noqa: F401
from openfisca_core.commons import ( # noqa: F401
apply_thresholds,
concat,
switch,
)

from openfisca_core.commons import apply_thresholds, concat, switch # noqa: F401

from openfisca_core.holders import ( # noqa: F401
set_input_dispatch_by_period,
set_input_divide_by_period,
)

from openfisca_core.indexed_enums import Enum # noqa: F401

from openfisca_core.parameters import ( # noqa: F401
Bracket,
load_parameter_file,
Parameter,
ParameterNode,
Scale,
Bracket,
Parameter,
ValuesHistory,
)

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

from openfisca_core.simulations import ( # noqa: F401
calculate_output_add,
calculate_output_divide,
)

from openfisca_core.variables import Variable # noqa: F401
2 changes: 1 addition & 1 deletion openfisca_core/parameters/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def _parse_child(child_name, child, child_path):
return parameters.Parameter(child_name, child, child_path)
elif 'brackets' in child:
return parameters.ParameterScale(child_name, child, child_path)
elif isinstance(child, dict) and all([periods.INSTANT_PATTERN.match(str(key)) for key in child.keys()]):
elif isinstance(child, dict) and all([periods.parse(str(key)) for key in child.keys()]):
return parameters.Parameter(child_name, child, child_path)
else:
return parameters.ParameterNode(child_name, data = child, file_path = child_path)
Expand Down
4 changes: 2 additions & 2 deletions openfisca_core/parameters/parameter.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def __init__(self, name: str, data: dict, file_path: Optional[str] = None) -> No

values_list = []
for instant_str in instants:
if not periods.INSTANT_PATTERN.match(instant_str):
if periods.parse(instant_str) is None:
raise ParameterParsingError(
"Invalid property '{}' in '{}'. Properties must be valid YYYY-MM-DD instants, such as 2017-01-15."
.format(instant_str, self.name),
Expand Down Expand Up @@ -126,7 +126,7 @@ def update(self, period = None, start = None, stop = None, value = None):
if start is None:
raise ValueError("You must provide either a start or a period")
start_str = str(start)
stop_str = str(stop.offset(1, 'day')) if stop else None
stop_str = str(stop.offset(1, periods.DAY)) if stop else None

old_values = self.values_list
new_values = []
Expand Down
Loading