From ea6a7bee84af2d3348e3243ac1769b3f38c32b80 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Thu, 28 Jul 2022 18:36:11 +0200 Subject: [PATCH 01/70] Add periods to doctests path --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index bb3ff50fc5..00be0143cb 100644 --- a/setup.cfg +++ b/setup.cfg @@ -14,7 +14,7 @@ extend-ignore = D hang-closing = true ignore = E128,E251,F403,F405,E501,RST301,W503,W504 in-place = true -include-in-doctest = openfisca_core/commons openfisca_core/types +include-in-doctest = openfisca_core/commons openfisca_core/periods openfisca_core/types rst-directives = attribute, deprecated, seealso, versionadded, versionchanged rst-roles = any, attr, class, exc, func, meth, obj strictness = short From 39a296a148c0720e08019805d4977fbcd55f7afb Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Thu, 28 Jul 2022 18:36:41 +0200 Subject: [PATCH 02/70] Add periods to tests path --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 00be0143cb..57a20b5618 100644 --- a/setup.cfg +++ b/setup.cfg @@ -41,7 +41,7 @@ skip_empty = true addopts = --doctest-modules --disable-pytest-warnings --showlocals doctest_optionflags = ELLIPSIS IGNORE_EXCEPTION_DETAIL NUMBER NORMALIZE_WHITESPACE python_files = **/*.py -testpaths = openfisca_core/commons openfisca_core/types tests +testpaths = openfisca_core/commons openfisca_core/periods openfisca_core/types tests [mypy] ignore_missing_imports = True From 8e2b5fac80b0d0c449536c70e9932ee85ec23977 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Thu, 28 Jul 2022 18:37:47 +0200 Subject: [PATCH 03/70] Ignore mypy periods' tests errors --- setup.cfg | 3 +++ 1 file changed, 3 insertions(+) diff --git a/setup.cfg b/setup.cfg index 57a20b5618..9e4290003c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -51,5 +51,8 @@ non_interactive = True [mypy-openfisca_core.commons.tests.*] ignore_errors = True +[mypy-openfisca_core.periods.tests.*] +ignore_errors = True + [mypy-openfisca_core.scripts.*] ignore_errors = True From 75f7f48530521e098fd0cd73eaa54f4d3d1fcc72 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Thu, 28 Jul 2022 19:53:11 +0200 Subject: [PATCH 04/70] Fix instants' docstrings --- openfisca_core/periods/instant_.py | 270 +++++++++++++---------------- 1 file changed, 121 insertions(+), 149 deletions(-) diff --git a/openfisca_core/periods/instant_.py b/openfisca_core/periods/instant_.py index c3da65f894..e10fd3bbee 100644 --- a/openfisca_core/periods/instant_.py +++ b/openfisca_core/periods/instant_.py @@ -6,32 +6,83 @@ class Instant(tuple): + """An instant in time (year, month, day). + + An :class:`.Instant` represents the most atomic and indivisible + legislation's time unit. + + Current implementation considers this unit to be a day, so + :obj:`instants <.Instant>` can be thought of as "day dates". + + Args: + tuple(tuple(int, int, int)): + The ``year``, ``month``, and ``day``, accordingly. + + Examples: + >>> instant = Instant((2021, 9, 13)) + + >>> repr(Instant) + "" + + >>> repr(instant) + 'Instant((2021, 9, 13))' + + >>> str(instant) + '2021-09-13' + + >>> dict([(instant, (2021, 9, 13))]) + {Instant((2021, 9, 13)): (2021, 9, 13)} + + >>> list(instant) + [2021, 9, 13] + + >>> instant[0] + 2021 + + >>> instant[0] in instant + True + + >>> len(instant) + 3 + + >>> instant == (2021, 9, 13) + True + + >>> instant != (2021, 9, 13) + False + + >>> instant > (2020, 9, 13) + True + + >>> instant < (2020, 9, 13) + False + + >>> instant >= (2020, 9, 13) + True + + >>> instant <= (2020, 9, 13) + False + + >>> instant.year + 2021 + + >>> instant.month + 9 + + >>> instant.day + 13 + + >>> instant.date + datetime.date(2021, 9, 13) + + >>> year, month, day = instant + + """ def __repr__(self): - """ - Transform instant to to its Python representation as a string. - - >>> repr(instant(2014)) - 'Instant((2014, 1, 1))' - >>> repr(instant('2014-2')) - 'Instant((2014, 2, 1))' - >>> repr(instant('2014-2-3')) - 'Instant((2014, 2, 3))' - """ return '{}({})'.format(self.__class__.__name__, super(Instant, self).__repr__()) def __str__(self): - """ - Transform instant to a string. - - >>> str(instant(2014)) - '2014-01-01' - >>> str(instant('2014-2')) - '2014-02-01' - >>> str(instant('2014-2-3')) - '2014-02-03' - - """ instant_str = config.str_by_instant_cache.get(self) if instant_str is None: config.str_by_instant_cache[self] = instant_str = self.date.isoformat() @@ -39,16 +90,6 @@ def __str__(self): @property def date(self): - """ - Convert instant to a date. - - >>> instant(2014).date - datetime.date(2014, 1, 1) - >>> instant('2014-2').date - datetime.date(2014, 2, 1) - >>> instant('2014-2-3').date - datetime.date(2014, 2, 3) - """ instant_date = config.date_by_instant_cache.get(self) if instant_date is None: config.date_by_instant_cache[self] = instant_date = datetime.date(*self) @@ -56,128 +97,69 @@ def date(self): @property def day(self): - """ - Extract day from instant. - - >>> instant(2014).day - 1 - >>> instant('2014-2').day - 1 - >>> instant('2014-2-3').day - 3 - """ return self[2] @property def month(self): - """ - Extract month from instant. - - >>> instant(2014).month - 1 - >>> instant('2014-2').month - 2 - >>> instant('2014-2-3').month - 2 - """ return self[1] def period(self, unit, size = 1): + """Creates a new :obj:`.Period` starting at :obj:`.Instant`. + + Args: + unit: ``day`` or ``month`` or ``year``. + size: How many of ``unit``. + + Returns: + A new object :obj:`.Period`. + + Raises: + :exc:`AssertionError`: When ``unit`` is not a date unit. + :exc:`AssertionError`: When ``size`` is not an unsigned :obj:`int`. + + Examples: + >>> Instant((2021, 9, 13)).period("year") + Period(('year', Instant((2021, 9, 13)), 1)) + + >>> Instant((2021, 9, 13)).period("month", 2) + Period(('month', Instant((2021, 9, 13)), 2)) + """ - Create a new period starting at instant. - - >>> instant(2014).period('month') - Period(('month', Instant((2014, 1, 1)), 1)) - >>> instant('2014-2').period('year', 2) - Period(('year', Instant((2014, 2, 1)), 2)) - >>> instant('2014-2-3').period('day', size = 2) - Period(('day', Instant((2014, 2, 3)), 2)) - """ + assert unit in (config.DAY, config.MONTH, config.YEAR), 'Invalid unit: {} of type {}'.format(unit, type(unit)) assert isinstance(size, int) and size >= 1, 'Invalid size: {} of type {}'.format(size, type(size)) return periods.Period((unit, self, size)) def offset(self, offset, unit): + """Increments/decrements the given instant with offset units. + + Args: + offset: How much of ``unit`` to offset. + unit: What to offset + + Returns: + :obj:`.Instant`: A new :obj:`.Instant` in time. + + Raises: + :exc:`AssertionError`: When ``unit`` is not a date unit. + :exc:`AssertionError`: When ``offset`` is not either ``first-of``, + ``last-of``, or any :obj:`int`. + + Examples: + >>> Instant((2020, 12, 31)).offset("first-of", "month") + Instant((2020, 12, 1)) + + >>> Instant((2020, 1, 1)).offset("last-of", "year") + Instant((2020, 12, 31)) + + >>> Instant((2020, 1, 1)).offset(1, "year") + Instant((2021, 1, 1)) + + >>> Instant((2020, 1, 1)).offset(-3, "day") + Instant((2019, 12, 29)) + """ - Increment (or decrement) the given instant with offset units. - - >>> instant(2014).offset(1, 'day') - Instant((2014, 1, 2)) - >>> instant(2014).offset(1, 'month') - Instant((2014, 2, 1)) - >>> instant(2014).offset(1, 'year') - Instant((2015, 1, 1)) - - >>> instant('2014-1-31').offset(1, 'day') - Instant((2014, 2, 1)) - >>> instant('2014-1-31').offset(1, 'month') - Instant((2014, 2, 28)) - >>> instant('2014-1-31').offset(1, 'year') - Instant((2015, 1, 31)) - - >>> instant('2011-2-28').offset(1, 'day') - Instant((2011, 3, 1)) - >>> instant('2011-2-28').offset(1, 'month') - Instant((2011, 3, 28)) - >>> instant('2012-2-29').offset(1, 'year') - Instant((2013, 2, 28)) - - >>> instant(2014).offset(-1, 'day') - Instant((2013, 12, 31)) - >>> instant(2014).offset(-1, 'month') - Instant((2013, 12, 1)) - >>> instant(2014).offset(-1, 'year') - Instant((2013, 1, 1)) - - >>> instant('2011-3-1').offset(-1, 'day') - Instant((2011, 2, 28)) - >>> instant('2011-3-31').offset(-1, 'month') - Instant((2011, 2, 28)) - >>> instant('2012-2-29').offset(-1, 'year') - Instant((2011, 2, 28)) - - >>> instant('2014-1-30').offset(3, 'day') - Instant((2014, 2, 2)) - >>> instant('2014-10-2').offset(3, 'month') - Instant((2015, 1, 2)) - >>> instant('2014-1-1').offset(3, 'year') - Instant((2017, 1, 1)) - - >>> instant(2014).offset(-3, 'day') - Instant((2013, 12, 29)) - >>> instant(2014).offset(-3, 'month') - Instant((2013, 10, 1)) - >>> instant(2014).offset(-3, 'year') - Instant((2011, 1, 1)) - - >>> instant(2014).offset('first-of', 'month') - Instant((2014, 1, 1)) - >>> instant('2014-2').offset('first-of', 'month') - Instant((2014, 2, 1)) - >>> instant('2014-2-3').offset('first-of', 'month') - Instant((2014, 2, 1)) - - >>> instant(2014).offset('first-of', 'year') - Instant((2014, 1, 1)) - >>> instant('2014-2').offset('first-of', 'year') - Instant((2014, 1, 1)) - >>> instant('2014-2-3').offset('first-of', 'year') - Instant((2014, 1, 1)) - - >>> instant(2014).offset('last-of', 'month') - Instant((2014, 1, 31)) - >>> instant('2014-2').offset('last-of', 'month') - Instant((2014, 2, 28)) - >>> instant('2012-2-3').offset('last-of', 'month') - Instant((2012, 2, 29)) - - >>> instant(2014).offset('last-of', 'year') - Instant((2014, 12, 31)) - >>> instant('2014-2').offset('last-of', 'year') - Instant((2014, 12, 31)) - >>> instant('2014-2-3').offset('last-of', 'year') - Instant((2014, 12, 31)) - """ + year, month, day = self assert unit in (config.DAY, config.MONTH, config.YEAR), 'Invalid unit: {} of type {}'.format(unit, type(unit)) if offset == 'first-of': @@ -236,14 +218,4 @@ def offset(self, offset, unit): @property def year(self): - """ - Extract year from instant. - - >>> instant(2014).year - 2014 - >>> instant('2014-2').year - 2014 - >>> instant('2014-2-3').year - 2014 - """ return self[0] From 9b6f5f37676aeab843db7c43df863b44009f6b61 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Thu, 28 Jul 2022 20:56:36 +0200 Subject: [PATCH 05/70] Fix periods' doctests --- openfisca_core/periods/period_.py | 634 ++++++++++++++++++------------ 1 file changed, 373 insertions(+), 261 deletions(-) diff --git a/openfisca_core/periods/period_.py b/openfisca_core/periods/period_.py index 808540f28a..af35b54e27 100644 --- a/openfisca_core/periods/period_.py +++ b/openfisca_core/periods/period_.py @@ -2,58 +2,160 @@ import calendar -from openfisca_core import periods -from openfisca_core.periods import config, helpers +from openfisca_core.periods import Instant, helpers, config class Period(tuple): - """ - Toolbox to handle date intervals. + """Toolbox to handle date intervals. + + A :class:`.Period` is a triple (``unit``, ``start``, ``size``). + + Attributes: + unit (:obj:`.DateUnit`): + Either an :meth:`~DateUnit.isoformat` unit (``day``, ``month``, + ``year``), an :meth:`~DateUnit.isocalendar` one (``week_day``, + ``week``, ``year``), or :obj:`~DateUnit.ETERNITY`. + start (:obj:`.Instant`): + The "instant" the :obj:`.Period` starts at. + size (:obj:`int`): + The amount of ``unit``, starting at ``start``, at least ``1``. + + Args: + fragments (tuple(.DateUnit, .Instant, int)): + The ``unit``, ``start``, and ``size``, accordingly. + + Examples: + >>> instant = Instant((2021, 9, 1)) + >>> period = Period(("year", instant, 3)) + + >>> repr(Period) + "" + + >>> repr(period) + "Period(('year', Instant((2021, 9, 1)), 3))" + + >>> str(period) + 'year:2021-09:3' + + >>> dict([period, instant]) + Traceback (most recent call last): + ValueError: dictionary update sequence element #0 has length 3; 2 is required + + >>> list(period) + ['year', Instant((2021, 9, 1)), 3] + + >>> period[0] + 'year' + + >>> period[0] in period + True + + >>> len(period) + 3 + + >>> period == Period(("year", instant, 3)) + True + + >>> period != Period(("year", instant, 3)) + False + + >>> period > Period(("year", instant, 3)) + False + + >>> period < Period(("year", instant, 3)) + False + + >>> period >= Period(("year", instant, 3)) + True - A period is a triple (unit, start, size), where unit is either "month" or "year", where start format is a - (year, month, day) triple, and where size is an integer > 1. + >>> period <= Period(("year", instant, 3)) + True + + >>> period.date + Traceback (most recent call last): + AssertionError: "date" is undefined for a period of size > 1 + + >>> Period(("year", instant, 1)).date + datetime.date(2021, 9, 1) + + >>> period.days + 1096 + + >>> period.size + 3 + + >>> period.size_in_months + 36 + + >>> period.size_in_days + 1096 + + >>> period.start + Instant((2021, 9, 1)) + + >>> period.stop + Instant((2024, 8, 31)) + + >>> period.unit + 'year' + + >>> period.last_3_months + Period(('month', Instant((2021, 6, 1)), 3)) + + >>> period.last_month + Period(('month', Instant((2021, 8, 1)), 1)) + + >>> period.last_year + Period(('year', Instant((2020, 1, 1)), 1)) + + >>> period.n_2 + Period(('year', Instant((2019, 1, 1)), 1)) + + >>> period.this_year + Period(('year', Instant((2021, 1, 1)), 1)) + + >>> period.first_month + Period(('month', Instant((2021, 9, 1)), 1)) + + >>> period.first_day + Period(('day', Instant((2021, 9, 1)), 1)) - Since a period is a triple it can be used as a dictionary key. """ def __repr__(self): - """ - Transform period to to its Python representation as a string. - - >>> repr(period('year', 2014)) - "Period(('year', Instant((2014, 1, 1)), 1))" - >>> repr(period('month', '2014-2')) - "Period(('month', Instant((2014, 2, 1)), 1))" - >>> repr(period('day', '2014-2-3')) - "Period(('day', Instant((2014, 2, 3)), 1))" - """ return '{}({})'.format(self.__class__.__name__, super(Period, self).__repr__()) def __str__(self): - """ - Transform period to a string. - - >>> str(period(YEAR, 2014)) - '2014' - - >>> str(period(YEAR, '2014-2')) - 'year:2014-02' - >>> str(period(MONTH, '2014-2')) - '2014-02' - - >>> str(period(YEAR, 2012, size = 2)) - 'year:2012:2' - >>> str(period(MONTH, 2012, size = 2)) - 'month:2012-01:2' - >>> str(period(MONTH, 2012, size = 12)) - '2012' - - >>> str(period(YEAR, '2012-3', size = 2)) - 'year:2012-03:2' - >>> str(period(MONTH, '2012-3', size = 2)) - 'month:2012-03:2' - >>> str(period(MONTH, '2012-3', size = 12)) - 'year:2012-03' + """Transform period to a string. + + Examples: + >>> str(Period(("year", Instant((2021, 1, 1)), 1))) + '2021' + + >>> str(Period(("year", Instant((2021, 2, 1)), 1))) + 'year:2021-02' + + >>> str(Period(("month", Instant((2021, 2, 1)), 1))) + '2021-02' + + >>> str(Period(("year", Instant((2021, 1, 1)), 2))) + 'year:2021:2' + + >>> str(Period(("month", Instant((2021, 1, 1)), 2))) + 'month:2021-01:2' + + >>> str(Period(("month", Instant((2021, 1, 1)), 12))) + '2021' + + >>> str(Period(("year", Instant((2021, 3, 1)), 2))) + 'year:2021-03:2' + + >>> str(Period(("month", Instant((2021, 3, 1)), 2))) + 'month:2021-03:2' + + >>> str(Period(("month", Instant((2021, 3, 1)), 12))) + 'year:2021-03' + """ unit, start_instant, size = self @@ -92,30 +194,8 @@ def date(self): @property def days(self): - """ - Count the number of days in period. - - >>> period('day', 2014).days - 365 - >>> period('month', 2014).days - 365 - >>> period('year', 2014).days - 365 - - >>> period('day', '2014-2').days - 28 - >>> period('month', '2014-2').days - 28 - >>> period('year', '2014-2').days - 365 - - >>> period('day', '2014-2-3').days - 1 - >>> period('month', '2014-2-3').days - 28 - >>> period('year', '2014-2-3').days - 365 - """ + """Count the number of days in period.""" + return (self.stop.date - self.start.date).days + 1 def intersection(self, start, stop): @@ -159,17 +239,19 @@ def intersection(self, start, stop): )) def get_subperiods(self, unit): - """ - Return the list of all the periods of unit ``unit`` contained in self. + """Return the list of all the periods of unit ``unit`` contained in self. Examples: + >>> period = Period(("year", Instant((2021, 1, 1)), 1)) + >>> period.get_subperiods("month") + [Period(('month', Instant((2021, 1, 1)), 1)), ...2021, 12, 1)), 1))] - >>> period('2017').get_subperiods(MONTH) - >>> [period('2017-01'), period('2017-02'), ... period('2017-12')] + >>> period = Period(("year", Instant((2021, 1, 1)), 2)) + >>> period.get_subperiods("year") + [Period(('year', Instant((2021, 1, 1)), 1)), ...((2022, 1, 1)), 1))] - >>> period('year:2014:2').get_subperiods(YEAR) - >>> [period('2014'), period('2015')] """ + if helpers.unit_weight(self.unit) < helpers.unit_weight(unit): raise ValueError('Cannot subdivide {0} into {1}'.format(self.unit, unit)) @@ -183,168 +265,200 @@ def get_subperiods(self, unit): return [self.first_day.offset(i, config.DAY) for i in range(self.size_in_days)] def offset(self, offset, unit = None): + """Increment (or decrement) the given period with offset units. + + Examples: + >>> Period(("day", Instant((2021, 1, 1)), 365)).offset(1) + Period(('day', Instant((2021, 1, 2)), 365)) + + >>> Period(("day", Instant((2021, 1, 1)), 365)).offset(1, "day") + Period(('day', Instant((2021, 1, 2)), 365)) + + >>> Period(("day", Instant((2021, 1, 1)), 365)).offset(1, "month") + Period(('day', Instant((2021, 2, 1)), 365)) + + >>> Period(("day", Instant((2021, 1, 1)), 365)).offset(1, "year") + Period(('day', Instant((2022, 1, 1)), 365)) + + >>> Period(("month", Instant((2021, 1, 1)), 12)).offset(1) + Period(('month', Instant((2021, 2, 1)), 12)) + + >>> Period(("month", Instant((2021, 1, 1)), 12)).offset(1, "day") + Period(('month', Instant((2021, 1, 2)), 12)) + + >>> Period(("month", Instant((2021, 1, 1)), 12)).offset(1, "month") + Period(('month', Instant((2021, 2, 1)), 12)) + + >>> Period(("month", Instant((2021, 1, 1)), 12)).offset(1, "year") + Period(('month', Instant((2022, 1, 1)), 12)) + + >>> Period(("year", Instant((2021, 1, 1)), 1)).offset(1) + Period(('year', Instant((2022, 1, 1)), 1)) + + >>> Period(("year", Instant((2021, 1, 1)), 1)).offset(1, "day") + Period(('year', Instant((2021, 1, 2)), 1)) + + >>> Period(("year", Instant((2021, 1, 1)), 1)).offset(1, "month") + Period(('year', Instant((2021, 2, 1)), 1)) + + >>> Period(("year", Instant((2021, 1, 1)), 1)).offset(1, "year") + Period(('year', Instant((2022, 1, 1)), 1)) + + >>> Period(("day", Instant((2011, 2, 28)), 1)).offset(1) + Period(('day', Instant((2011, 3, 1)), 1)) + + >>> Period(("month", Instant((2011, 2, 28)), 1)).offset(1) + Period(('month', Instant((2011, 3, 28)), 1)) + + >>> Period(("year", Instant((2011, 2, 28)), 1)).offset(1) + Period(('year', Instant((2012, 2, 28)), 1)) + + >>> Period(("day", Instant((2011, 3, 1)), 1)).offset(-1) + Period(('day', Instant((2011, 2, 28)), 1)) + + >>> Period(("month", Instant((2011, 3, 1)), 1)).offset(-1) + Period(('month', Instant((2011, 2, 1)), 1)) + + >>> Period(("year", Instant((2011, 3, 1)), 1)).offset(-1) + Period(('year', Instant((2010, 3, 1)), 1)) + + >>> Period(("day", Instant((2014, 1, 30)), 1)).offset(3) + Period(('day', Instant((2014, 2, 2)), 1)) + + >>> Period(("month", Instant((2014, 1, 30)), 1)).offset(3) + Period(('month', Instant((2014, 4, 30)), 1)) + + >>> Period(("year", Instant((2014, 1, 30)), 1)).offset(3) + Period(('year', Instant((2017, 1, 30)), 1)) + + >>> Period(("day", Instant((2021, 1, 1)), 365)).offset(-3) + Period(('day', Instant((2020, 12, 29)), 365)) + + >>> Period(("month", Instant((2021, 1, 1)), 12)).offset(-3) + Period(('month', Instant((2020, 10, 1)), 12)) + + >>> Period(("year", Instant((2014, 1, 1)), 1)).offset(-3) + Period(('year', Instant((2011, 1, 1)), 1)) + + >>> Period(("day", Instant((2014, 2, 3)), 1)).offset("first-of", "month") + Period(('day', Instant((2014, 2, 1)), 1)) + + >>> Period(("day", Instant((2014, 2, 3)), 1)).offset("first-of", "year") + Period(('day', Instant((2014, 1, 1)), 1)) + + >>> Period(("day", Instant((2014, 2, 3)), 4)).offset("first-of", "month") + Period(('day', Instant((2014, 2, 1)), 4)) + + >>> Period(("day", Instant((2014, 2, 3)), 4)).offset("first-of", "year") + Period(('day', Instant((2014, 1, 1)), 4)) + + >>> Period(("month", Instant((2014, 2, 3)), 1)).offset("first-of") + Period(('month', Instant((2014, 2, 1)), 1)) + + >>> Period(("month", Instant((2014, 2, 3)), 1)).offset("first-of", "month") + Period(('month', Instant((2014, 2, 1)), 1)) + + >>> Period(("month", Instant((2014, 2, 3)), 1)).offset("first-of", "year") + Period(('month', Instant((2014, 1, 1)), 1)) + + >>> Period(("month", Instant((2014, 2, 3)), 4)).offset("first-of") + Period(('month', Instant((2014, 2, 1)), 4)) + + >>> Period(("month", Instant((2014, 2, 3)), 4)).offset("first-of", "month") + Period(('month', Instant((2014, 2, 1)), 4)) + + >>> Period(("month", Instant((2014, 2, 3)), 4)).offset("first-of", "year") + Period(('month', Instant((2014, 1, 1)), 4)) + + >>> Period(("year", Instant((2014, 1, 30)), 1)).offset("first-of") + Period(('year', Instant((2014, 1, 1)), 1)) + + >>> Period(("year", Instant((2014, 1, 30)), 1)).offset("first-of", "month") + Period(('year', Instant((2014, 1, 1)), 1)) + + >>> Period(("year", Instant((2014, 1, 30)), 1)).offset("first-of", "year") + Period(('year', Instant((2014, 1, 1)), 1)) + + >>> Period(("year", Instant((2014, 2, 3)), 1)).offset("first-of") + Period(('year', Instant((2014, 1, 1)), 1)) + + >>> Period(("year", Instant((2014, 2, 3)), 1)).offset("first-of", "month") + Period(('year', Instant((2014, 2, 1)), 1)) + + >>> Period(("year", Instant((2014, 2, 3)), 1)).offset("first-of", "year") + Period(('year', Instant((2014, 1, 1)), 1)) + + >>> Period(("day", Instant((2014, 2, 3)), 1)).offset("last-of", "month") + Period(('day', Instant((2014, 2, 28)), 1)) + + >>> Period(("day", Instant((2014, 2, 3)), 1)).offset("last-of", "year") + Period(('day', Instant((2014, 12, 31)), 1)) + + >>> Period(("day", Instant((2014, 2, 3)), 4)).offset("last-of", "month") + Period(('day', Instant((2014, 2, 28)), 4)) + + >>> Period(("day", Instant((2014, 2, 3)), 4)).offset("last-of", "year") + Period(('day', Instant((2014, 12, 31)), 4)) + + >>> Period(("month", Instant((2014, 2, 3)), 1)).offset("last-of") + Period(('month', Instant((2014, 2, 28)), 1)) + + >>> Period(("month", Instant((2014, 2, 3)), 1)).offset("last-of", "month") + Period(('month', Instant((2014, 2, 28)), 1)) + + >>> Period(("month", Instant((2014, 2, 3)), 1)).offset("last-of", "year") + Period(('month', Instant((2014, 12, 31)), 1)) + + >>> Period(("month", Instant((2014, 2, 3)), 4)).offset("last-of") + Period(('month', Instant((2014, 2, 28)), 4)) + + >>> Period(("month", Instant((2014, 2, 3)), 4)).offset("last-of", "month") + Period(('month', Instant((2014, 2, 28)), 4)) + + >>> Period(("month", Instant((2014, 2, 3)), 4)).offset("last-of", "year") + Period(('month', Instant((2014, 12, 31)), 4)) + + >>> Period(("year", Instant((2014, 2, 3)), 1)).offset("last-of") + Period(('year', Instant((2014, 12, 31)), 1)) + + >>> Period(("year", Instant((2014, 1, 1)), 1)).offset("last-of", "month") + Period(('year', Instant((2014, 1, 31)), 1)) + + >>> Period(("year", Instant((2014, 2, 3)), 1)).offset("last-of", "year") + Period(('year', Instant((2014, 12, 31)), 1)) + + >>> Period(("year", Instant((2014, 2, 3)), 1)).offset("last-of") + Period(('year', Instant((2014, 12, 31)), 1)) + + >>> Period(("year", Instant((2014, 2, 3)), 1)).offset("last-of", "month") + Period(('year', Instant((2014, 2, 28)), 1)) + + >>> Period(("year", Instant((2014, 2, 3)), 1)).offset("last-of", "year") + Period(('year', Instant((2014, 12, 31)), 1)) + """ - Increment (or decrement) the given period with offset units. - - >>> period('day', 2014).offset(1) - Period(('day', Instant((2014, 1, 2)), 365)) - >>> period('day', 2014).offset(1, 'day') - Period(('day', Instant((2014, 1, 2)), 365)) - >>> period('day', 2014).offset(1, 'month') - Period(('day', Instant((2014, 2, 1)), 365)) - >>> period('day', 2014).offset(1, 'year') - Period(('day', Instant((2015, 1, 1)), 365)) - - >>> period('month', 2014).offset(1) - Period(('month', Instant((2014, 2, 1)), 12)) - >>> period('month', 2014).offset(1, 'day') - Period(('month', Instant((2014, 1, 2)), 12)) - >>> period('month', 2014).offset(1, 'month') - Period(('month', Instant((2014, 2, 1)), 12)) - >>> period('month', 2014).offset(1, 'year') - Period(('month', Instant((2015, 1, 1)), 12)) - - >>> period('year', 2014).offset(1) - Period(('year', Instant((2015, 1, 1)), 1)) - >>> period('year', 2014).offset(1, 'day') - Period(('year', Instant((2014, 1, 2)), 1)) - >>> period('year', 2014).offset(1, 'month') - Period(('year', Instant((2014, 2, 1)), 1)) - >>> period('year', 2014).offset(1, 'year') - Period(('year', Instant((2015, 1, 1)), 1)) - - >>> period('day', '2011-2-28').offset(1) - Period(('day', Instant((2011, 3, 1)), 1)) - >>> period('month', '2011-2-28').offset(1) - Period(('month', Instant((2011, 3, 28)), 1)) - >>> period('year', '2011-2-28').offset(1) - Period(('year', Instant((2012, 2, 28)), 1)) - - >>> period('day', '2011-3-1').offset(-1) - Period(('day', Instant((2011, 2, 28)), 1)) - >>> period('month', '2011-3-1').offset(-1) - Period(('month', Instant((2011, 2, 1)), 1)) - >>> period('year', '2011-3-1').offset(-1) - Period(('year', Instant((2010, 3, 1)), 1)) - - >>> period('day', '2014-1-30').offset(3) - Period(('day', Instant((2014, 2, 2)), 1)) - >>> period('month', '2014-1-30').offset(3) - Period(('month', Instant((2014, 4, 30)), 1)) - >>> period('year', '2014-1-30').offset(3) - Period(('year', Instant((2017, 1, 30)), 1)) - - >>> period('day', 2014).offset(-3) - Period(('day', Instant((2013, 12, 29)), 365)) - >>> period('month', 2014).offset(-3) - Period(('month', Instant((2013, 10, 1)), 12)) - >>> period('year', 2014).offset(-3) - Period(('year', Instant((2011, 1, 1)), 1)) - - >>> period('day', '2014-2-3').offset('first-of', 'month') - Period(('day', Instant((2014, 2, 1)), 1)) - >>> period('day', '2014-2-3').offset('first-of', 'year') - Period(('day', Instant((2014, 1, 1)), 1)) - - >>> period('day', '2014-2-3', 4).offset('first-of', 'month') - Period(('day', Instant((2014, 2, 1)), 4)) - >>> period('day', '2014-2-3', 4).offset('first-of', 'year') - Period(('day', Instant((2014, 1, 1)), 4)) - - >>> period('month', '2014-2-3').offset('first-of') - Period(('month', Instant((2014, 2, 1)), 1)) - >>> period('month', '2014-2-3').offset('first-of', 'month') - Period(('month', Instant((2014, 2, 1)), 1)) - >>> period('month', '2014-2-3').offset('first-of', 'year') - Period(('month', Instant((2014, 1, 1)), 1)) - - >>> period('month', '2014-2-3', 4).offset('first-of') - Period(('month', Instant((2014, 2, 1)), 4)) - >>> period('month', '2014-2-3', 4).offset('first-of', 'month') - Period(('month', Instant((2014, 2, 1)), 4)) - >>> period('month', '2014-2-3', 4).offset('first-of', 'year') - Period(('month', Instant((2014, 1, 1)), 4)) - - >>> period('year', 2014).offset('first-of') - Period(('year', Instant((2014, 1, 1)), 1)) - >>> period('year', 2014).offset('first-of', 'month') - Period(('year', Instant((2014, 1, 1)), 1)) - >>> period('year', 2014).offset('first-of', 'year') - Period(('year', Instant((2014, 1, 1)), 1)) - - >>> period('year', '2014-2-3').offset('first-of') - Period(('year', Instant((2014, 1, 1)), 1)) - >>> period('year', '2014-2-3').offset('first-of', 'month') - Period(('year', Instant((2014, 2, 1)), 1)) - >>> period('year', '2014-2-3').offset('first-of', 'year') - Period(('year', Instant((2014, 1, 1)), 1)) - - >>> period('day', '2014-2-3').offset('last-of', 'month') - Period(('day', Instant((2014, 2, 28)), 1)) - >>> period('day', '2014-2-3').offset('last-of', 'year') - Period(('day', Instant((2014, 12, 31)), 1)) - - >>> period('day', '2014-2-3', 4).offset('last-of', 'month') - Period(('day', Instant((2014, 2, 28)), 4)) - >>> period('day', '2014-2-3', 4).offset('last-of', 'year') - Period(('day', Instant((2014, 12, 31)), 4)) - - >>> period('month', '2014-2-3').offset('last-of') - Period(('month', Instant((2014, 2, 28)), 1)) - >>> period('month', '2014-2-3').offset('last-of', 'month') - Period(('month', Instant((2014, 2, 28)), 1)) - >>> period('month', '2014-2-3').offset('last-of', 'year') - Period(('month', Instant((2014, 12, 31)), 1)) - - >>> period('month', '2014-2-3', 4).offset('last-of') - Period(('month', Instant((2014, 2, 28)), 4)) - >>> period('month', '2014-2-3', 4).offset('last-of', 'month') - Period(('month', Instant((2014, 2, 28)), 4)) - >>> period('month', '2014-2-3', 4).offset('last-of', 'year') - Period(('month', Instant((2014, 12, 31)), 4)) - - >>> period('year', 2014).offset('last-of') - Period(('year', Instant((2014, 12, 31)), 1)) - >>> period('year', 2014).offset('last-of', 'month') - Period(('year', Instant((2014, 1, 31)), 1)) - >>> period('year', 2014).offset('last-of', 'year') - Period(('year', Instant((2014, 12, 31)), 1)) - - >>> period('year', '2014-2-3').offset('last-of') - Period(('year', Instant((2014, 12, 31)), 1)) - >>> period('year', '2014-2-3').offset('last-of', 'month') - Period(('year', Instant((2014, 2, 28)), 1)) - >>> period('year', '2014-2-3').offset('last-of', 'year') - Period(('year', Instant((2014, 12, 31)), 1)) - """ + return self.__class__((self[0], self[1].offset(offset, self[0] if unit is None else unit), self[2])) def contains(self, other: Period) -> bool: + """Returns ``True`` if the period contains ``other``. + + For instance, ``period(2015)`` contains ``period(2015-01)``. + """ - Returns ``True`` if the period contains ``other``. For instance, ``period(2015)`` contains ``period(2015-01)`` - """ + return self.start <= other.start and self.stop >= other.stop @property def size(self): - """ - Return the size of the period. + """Return the size of the period.""" - >>> period('month', '2012-2-29', 4).size - 4 - """ return self[2] @property def size_in_months(self): - """ - Return the size of the period in months. + """Return the size of the period in months.""" - >>> period('month', '2012-2-29', 4).size_in_months - 4 - >>> period('year', '2012', 1).size_in_months - 12 - """ if (self[0] == config.MONTH): return self[2] if(self[0] == config.YEAR): @@ -353,14 +467,8 @@ def size_in_months(self): @property def size_in_days(self): - """ - Return the size of the period in days. + """Return the size of the period in days.""" - >>> period('month', '2012-2-29', 4).size_in_days - 28 - >>> period('year', '2012', 1).size_in_days - 366 - """ unit, instant, length = self if unit == config.DAY: @@ -372,45 +480,49 @@ def size_in_days(self): raise ValueError("Cannot calculate number of days in {0}".format(unit)) @property - def start(self) -> periods.Instant: - """ - Return the first day of the period as an Instant instance. + def start(self) -> Instant: + """Return the first day of the period as an Instant instance.""" - >>> period('month', '2012-2-29', 4).start - Instant((2012, 2, 29)) - """ return self[1] @property - def stop(self) -> periods.Instant: - """ - Return the last day of the period as an Instant instance. - - >>> period('year', 2014).stop - Instant((2014, 12, 31)) - >>> period('month', 2014).stop - Instant((2014, 12, 31)) - >>> period('day', 2014).stop - Instant((2014, 12, 31)) - - >>> period('year', '2012-2-29').stop - Instant((2013, 2, 28)) - >>> period('month', '2012-2-29').stop - Instant((2012, 3, 28)) - >>> period('day', '2012-2-29').stop - Instant((2012, 2, 29)) - - >>> period('year', '2012-2-29', 2).stop - Instant((2014, 2, 28)) - >>> period('month', '2012-2-29', 2).stop - Instant((2012, 4, 28)) - >>> period('day', '2012-2-29', 2).stop - Instant((2012, 3, 1)) + def stop(self) -> Instant: + """Return the last day of the period as an Instant instance. + + Examples: + >>> Period(("year", Instant((2022, 1, 1)), 1)).stop + Instant((2022, 12, 31)) + + >>> Period(("month", Instant((2022, 1, 1)), 12)).stop + Instant((2022, 12, 31)) + + >>> Period(("day", Instant((2022, 1, 1)), 365)).stop + Instant((2022, 12, 31)) + + >>> Period(("year", Instant((2012, 2, 29)), 1)).stop + Instant((2013, 2, 28)) + + >>> Period(("month", Instant((2012, 2, 29)), 1)).stop + Instant((2012, 3, 28)) + + >>> Period(("day", Instant((2012, 2, 29)), 1)).stop + Instant((2012, 2, 29)) + + >>> Period(("year", Instant((2012, 2, 29)), 2)).stop + Instant((2014, 2, 28)) + + >>> Period(("month", Instant((2012, 2, 29)), 2)).stop + Instant((2012, 4, 28)) + + >>> Period(("day", Instant((2012, 2, 29)), 2)).stop + Instant((2012, 3, 1)) + """ + unit, start_instant, size = self year, month, day = start_instant if unit == config.ETERNITY: - return periods.Instant((float("inf"), float("inf"), float("inf"))) + return Instant((float("inf"), float("inf"), float("inf"))) if unit == 'day': if size > 1: day += size - 1 @@ -446,7 +558,7 @@ def stop(self) -> periods.Instant: year += 1 month = 1 day -= month_last_day - return periods.Instant((year, month, day)) + return Instant((year, month, day)) @property def unit(self): @@ -464,19 +576,19 @@ def last_month(self): @property def last_year(self): - return self.start.offset('first-of', 'year').period('year').offset(-1) + return self.start.offset("first-of", "year").period('year').offset(-1) @property def n_2(self): - return self.start.offset('first-of', 'year').period('year').offset(-2) + return self.start.offset("first-of", "year").period('year').offset(-2) @property def this_year(self): - return self.start.offset('first-of', 'year').period('year') + return self.start.offset("first-of", "year").period('year') @property def first_month(self): - return self.start.offset('first-of', 'month').period('month') + return self.start.offset("first-of", "month").period('month') @property def first_day(self): From b41bb5cba4dee886bad601421e3819898feef377 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Thu, 28 Jul 2022 21:44:54 +0200 Subject: [PATCH 06/70] Fix periods' helpers doctests --- openfisca_core/periods/helpers.py | 200 ++++++++++++++++++++--------- openfisca_core/periods/instant_.py | 6 +- openfisca_core/periods/period_.py | 11 +- setup.cfg | 2 +- 4 files changed, 149 insertions(+), 70 deletions(-) diff --git a/openfisca_core/periods/helpers.py b/openfisca_core/periods/helpers.py index 9ddf794d06..bc098e4c8a 100644 --- a/openfisca_core/periods/helpers.py +++ b/openfisca_core/periods/helpers.py @@ -1,8 +1,9 @@ import datetime import os -from openfisca_core import periods -from openfisca_core.periods import config +from . import config +from .instant_ import Instant +from .period_ import Period def N_(message): @@ -12,52 +13,86 @@ def N_(message): def instant(instant): """Return a new instant, aka a triple of integers (year, month, day). - >>> instant(2014) - Instant((2014, 1, 1)) - >>> instant('2014') - Instant((2014, 1, 1)) - >>> instant('2014-02') - Instant((2014, 2, 1)) - >>> instant('2014-3-2') - Instant((2014, 3, 2)) - >>> instant(instant('2014-3-2')) - Instant((2014, 3, 2)) - >>> instant(period('month', '2014-3-2')) - Instant((2014, 3, 2)) - - >>> instant(None) + Args: + instant: An ``instant-like`` object. + + Returns: + None: When ``instant`` is None. + :obj:`.Instant`: Otherwise. + + Raises: + :exc:`ValueError`: When the arguments were invalid, like "2021-32-13". + + Examples: + >>> instant((2021,)) + Instant((2021, 1, 1)) + + >>> instant((2021, 9)) + Instant((2021, 9, 1)) + + >>> instant(datetime.date(2021, 9, 16)) + Instant((2021, 9, 16)) + + >>> instant(Instant((2021, 9, 16))) + Instant((2021, 9, 16)) + + >>> instant(Period(("year", Instant((2021, 9, 16)), 1))) + Instant((2021, 9, 16)) + + >>> instant(2021) + Instant((2021, 1, 1)) + + >>> instant("2021") + Instant((2021, 1, 1)) + """ + if instant is None: return None - if isinstance(instant, periods.Instant): + if isinstance(instant, Instant): return instant if isinstance(instant, str): if not config.INSTANT_PATTERN.match(instant): raise ValueError("'{}' is not a valid instant. Instants are described using the 'YYYY-MM-DD' format, for instance '2015-06-15'.".format(instant)) - instant = periods.Instant( + instant = Instant( int(fragment) for fragment in instant.split('-', 2)[:3] ) elif isinstance(instant, datetime.date): - instant = periods.Instant((instant.year, instant.month, instant.day)) + instant = Instant((instant.year, instant.month, instant.day)) elif isinstance(instant, int): instant = (instant,) elif isinstance(instant, list): assert 1 <= len(instant) <= 3 instant = tuple(instant) - elif isinstance(instant, periods.Period): + elif isinstance(instant, Period): instant = instant.start else: assert isinstance(instant, tuple), instant assert 1 <= len(instant) <= 3 if len(instant) == 1: - return periods.Instant((instant[0], 1, 1)) + return Instant((instant[0], 1, 1)) if len(instant) == 2: - return periods.Instant((instant[0], instant[1], 1)) - return periods.Instant(instant) + return Instant((instant[0], instant[1], 1)) + return Instant(instant) def instant_date(instant): + """Returns the date representation of an :class:`.Instant`. + + Args: + instant (:obj:`.Instant`, optional): + + Returns: + None: When ``instant`` is None. + :obj:`datetime.date`: Otherwise. + + Examples: + >>> instant_date(Instant((2021, 1, 1))) + datetime.date(2021, 1, 1) + + """ + if instant is None: return None instant_date = config.date_by_instant_cache.get(instant) @@ -67,33 +102,63 @@ def instant_date(instant): def period(value): - """Return a new period, aka a triple (unit, start_instant, size). - - >>> period('2014') - Period((YEAR, Instant((2014, 1, 1)), 1)) - >>> period('year:2014') - Period((YEAR, Instant((2014, 1, 1)), 1)) - - >>> period('2014-2') - Period((MONTH, Instant((2014, 2, 1)), 1)) - >>> period('2014-02') - Period((MONTH, Instant((2014, 2, 1)), 1)) - >>> period('month:2014-2') - Period((MONTH, Instant((2014, 2, 1)), 1)) - - >>> period('year:2014-2') - Period((YEAR, Instant((2014, 2, 1)), 1)) + """Returns a new period, aka a triple (unit, start_instant, size). + + Args: + value: A ``period-like`` object. + + Returns: + :obj:`.Period`: A period. + + Raises: + :exc:`ValueError`: When the arguments were invalid, like "2021-32-13". + + Examples: + >>> period(Period(("year", Instant((2021, 1, 1)), 1))) + Period(('year', Instant((2021, 1, 1)), 1)) + + >>> period(Instant((2021, 1, 1))) + Period(('day', Instant((2021, 1, 1)), 1)) + + >>> period("eternity") + Period(('eternity', Instant((1, 1, 1)), inf)) + + >>> period(2021) + Period(('year', Instant((2021, 1, 1)), 1)) + + >>> period("2014") + Period(('year', Instant((2014, 1, 1)), 1)) + + >>> period("year:2014") + Period(('year', Instant((2014, 1, 1)), 1)) + + >>> period("month:2014-2") + Period(('month', Instant((2014, 2, 1)), 1)) + + >>> period("year:2014-2") + Period(('year', Instant((2014, 2, 1)), 1)) + + >>> period("day:2014-2-2") + Period(('day', Instant((2014, 2, 2)), 1)) + + >>> period("day:2014-2-2:3") + Period(('day', Instant((2014, 2, 2)), 3)) + """ - if isinstance(value, periods.Period): + + if isinstance(value, Period): return value - if isinstance(value, periods.Instant): - return periods.Period((config.DAY, value, 1)) + if isinstance(value, Instant): + return Period((config.DAY, value, 1)) def parse_simple_period(value): + """Parses simple periods respecting the ISO format. + + Such as 2012 or 2015-03. + """ - Parses simple periods respecting the ISO format, such as 2012 or 2015-03 - """ + try: date = datetime.datetime.strptime(value, '%Y') except ValueError: @@ -105,11 +170,11 @@ def parse_simple_period(value): except ValueError: return None else: - return periods.Period((config.DAY, periods.Instant((date.year, date.month, date.day)), 1)) + return Period((config.DAY, Instant((date.year, date.month, date.day)), 1)) else: - return periods.Period((config.MONTH, periods.Instant((date.year, date.month, 1)), 1)) + return Period((config.MONTH, Instant((date.year, date.month, 1)), 1)) else: - return periods.Period((config.YEAR, periods.Instant((date.year, date.month, 1)), 1)) + return Period((config.YEAR, Instant((date.year, date.month, 1)), 1)) def raise_error(value): message = os.linesep.join([ @@ -120,11 +185,11 @@ def raise_error(value): raise ValueError(message) if value == 'ETERNITY' or value == config.ETERNITY: - return periods.Period(('eternity', instant(datetime.date.min), float("inf"))) + return Period(('eternity', instant(datetime.date.min), float("inf"))) # check the type if isinstance(value, int): - return periods.Period((config.YEAR, periods.Instant((value, 1, 1)), 1)) + return Period((config.YEAR, Instant((value, 1, 1)), 1)) if not isinstance(value, str): raise_error(value) @@ -166,22 +231,37 @@ def raise_error(value): if unit_weight(base_period.unit) > unit_weight(unit): raise_error(value) - return periods.Period((unit, base_period.start, size)) + return Period((unit, base_period.start, size)) def key_period_size(period): - """ - Defines a key in order to sort periods by length. It uses two aspects : first unit then size + """Defines a key in order to sort periods by length. + + It uses two aspects: first, ``unit``, then, ``size``. + + Args: + period: An :mod:`.openfisca_core` :obj:`.Period`. + + Returns: + :obj:`str`: A string. + + Examples: + >>> instant = Instant((2021, 9, 14)) + >>> period = Period(("day", instant, 1)) + >>> key_period_size(period) + '100_1' + + >>> period = Period(("month", instant, 2)) + >>> key_period_size(period) + '200_2' - :param period: an OpenFisca period - :return: a string + >>> period = Period(("year", instant, 3)) + >>> key_period_size(period) + '300_3' - >>> key_period_size(period('2014')) - '2_1' - >>> key_period_size(period('2013')) - '2_1' - >>> key_period_size(period('2014-01')) - '1_1' + >>> period = Period(("eternity", instant, 4)) + >>> key_period_size(period) + '400_4' """ diff --git a/openfisca_core/periods/instant_.py b/openfisca_core/periods/instant_.py index e10fd3bbee..a9a5e32779 100644 --- a/openfisca_core/periods/instant_.py +++ b/openfisca_core/periods/instant_.py @@ -1,8 +1,8 @@ import calendar import datetime -from openfisca_core import periods -from openfisca_core.periods import config +from .. import periods +from . import config class Instant(tuple): @@ -15,7 +15,7 @@ class Instant(tuple): :obj:`instants <.Instant>` can be thought of as "day dates". Args: - tuple(tuple(int, int, int)): + (tuple(tuple(int, int, int))): The ``year``, ``month``, and ``day``, accordingly. Examples: diff --git a/openfisca_core/periods/period_.py b/openfisca_core/periods/period_.py index af35b54e27..1c9942c681 100644 --- a/openfisca_core/periods/period_.py +++ b/openfisca_core/periods/period_.py @@ -2,7 +2,8 @@ import calendar -from openfisca_core.periods import Instant, helpers, config +from . import config, helpers +from .instant_ import Instant class Period(tuple): @@ -11,17 +12,15 @@ class Period(tuple): A :class:`.Period` is a triple (``unit``, ``start``, ``size``). Attributes: - unit (:obj:`.DateUnit`): - Either an :meth:`~DateUnit.isoformat` unit (``day``, ``month``, - ``year``), an :meth:`~DateUnit.isocalendar` one (``week_day``, - ``week``, ``year``), or :obj:`~DateUnit.ETERNITY`. + unit (:obj:`str`): + Either ``year``, ``month``, ``day`` or ``eternity``. start (:obj:`.Instant`): The "instant" the :obj:`.Period` starts at. size (:obj:`int`): The amount of ``unit``, starting at ``start``, at least ``1``. Args: - fragments (tuple(.DateUnit, .Instant, int)): + (tuple(tuple(str, .Instant, int))): The ``unit``, ``start``, and ``size``, accordingly. Examples: diff --git a/setup.cfg b/setup.cfg index 9e4290003c..889f55560f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -16,7 +16,7 @@ ignore = E128,E251,F403,F405,E501,RST301,W503,W504 in-place = true include-in-doctest = openfisca_core/commons openfisca_core/periods openfisca_core/types rst-directives = attribute, deprecated, seealso, versionadded, versionchanged -rst-roles = any, attr, class, exc, func, meth, obj +rst-roles = any, attr, class, exc, func, meth, mod, obj strictness = short [pylint.message_control] From 7a392f0dcfcb6ed8625306a67bdd8dbfd0c4dff5 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Sat, 30 Jul 2022 15:20:06 +0200 Subject: [PATCH 07/70] Add tests to periods.instant --- openfisca_core/periods/tests/__init__.py | 0 openfisca_core/periods/tests/test_helpers.py | 64 ++++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 openfisca_core/periods/tests/__init__.py create mode 100644 openfisca_core/periods/tests/test_helpers.py diff --git a/openfisca_core/periods/tests/__init__.py b/openfisca_core/periods/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openfisca_core/periods/tests/test_helpers.py b/openfisca_core/periods/tests/test_helpers.py new file mode 100644 index 0000000000..c20445b72d --- /dev/null +++ b/openfisca_core/periods/tests/test_helpers.py @@ -0,0 +1,64 @@ +import datetime + +import pytest + +from openfisca_core import periods +from openfisca_core.periods import DateUnit, Instant, Period + + +def test_instant(): + assert periods.instant((2022, 1, 1)) == Instant((2022, 1, 1)) + + +@pytest.mark.parametrize("arg, expected", [ + [None, None], + [datetime.date(1, 1, 1), Instant((1, 1, 1))], + [Instant((1, 1, 1)), Instant((1, 1, 1))], + [Period((DateUnit.DAY, Instant((1, 1, 1)), 365)), Instant((1, 1, 1))], + [-1, Instant((-1, 1, 1))], + [0, Instant((0, 1, 1))], + [1, Instant((1, 1, 1))], + [999, Instant((999, 1, 1))], + [1000, Instant((1000, 1, 1))], + ["1000", Instant((1000, 1, 1))], + ["1000-01-01", Instant((1000, 1, 1))], + [(None,), Instant((None, 1, 1))], + [(None, None), Instant((None, None, 1))], + [(None, None, None), Instant((None, None, None))], + [(datetime.date(1, 1, 1),), Instant((datetime.date(1, 1, 1), 1, 1))], + [(Instant((1, 1, 1)),), Instant((Instant((1, 1, 1)), 1, 1))], + [(Period((DateUnit.DAY, Instant((1, 1, 1)), 365)),), Instant((Period((DateUnit.DAY, Instant((1, 1, 1)), 365)), 1, 1))], + [(-1,), Instant((-1, 1, 1))], + [(-1, -1), Instant((-1, -1, 1))], + [(-1, -1, -1), Instant((-1, -1, -1))], + [("-1",), Instant(("-1", 1, 1))], + [("-1", "-1"), Instant(("-1", "-1", 1))], + [("-1", "-1", "-1"), Instant(("-1", "-1", "-1"))], + [("1-1",), Instant(("1-1", 1, 1))], + [("1-1-1",), Instant(("1-1-1", 1, 1))], + ]) +def test_instant_with_a_valid_argument(arg, expected): + assert periods.instant(arg) == expected + + +@pytest.mark.parametrize("arg, error", [ + ["1000-0", ValueError], + ["1000-0-0", ValueError], + ["1000-1", ValueError], + ["1000-1-1", ValueError], + ["1", ValueError], + ["a", ValueError], + ["999", ValueError], + ["1:1000-01-01", ValueError], + ["a:1000-01-01", ValueError], + ["year:1000-01-01", ValueError], + ["year:1000-01-01:1", ValueError], + ["year:1000-01-01:3", ValueError], + ["1000-01-01:a", ValueError], + ["1000-01-01:1", ValueError], + [(), AssertionError], + [(None, None, None, None), AssertionError], + ]) +def test_instant_with_an_invalid_argument(arg, error): + with pytest.raises(error): + periods.instant(arg) \ No newline at end of file From dcabc2b2b606769b20e1aa82b7a1cdcec5776a8f Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Sat, 30 Jul 2022 16:17:00 +0200 Subject: [PATCH 08/70] Add tests to periods.instant_date --- .../periods/tests/helpers/__init__.py | 0 .../test_instant.py} | 6 +-- .../tests/helpers/test_instant_date.py | 42 +++++++++++++++++++ 3 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 openfisca_core/periods/tests/helpers/__init__.py rename openfisca_core/periods/tests/{test_helpers.py => helpers/test_instant.py} (94%) create mode 100644 openfisca_core/periods/tests/helpers/test_instant_date.py diff --git a/openfisca_core/periods/tests/helpers/__init__.py b/openfisca_core/periods/tests/helpers/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openfisca_core/periods/tests/test_helpers.py b/openfisca_core/periods/tests/helpers/test_instant.py similarity index 94% rename from openfisca_core/periods/tests/test_helpers.py rename to openfisca_core/periods/tests/helpers/test_instant.py index c20445b72d..c96a467f54 100644 --- a/openfisca_core/periods/tests/test_helpers.py +++ b/openfisca_core/periods/tests/helpers/test_instant.py @@ -7,7 +7,7 @@ def test_instant(): - assert periods.instant((2022, 1, 1)) == Instant((2022, 1, 1)) + assert periods.instant((2022, 1, 1)) == Instant((2022, 1, 1)) @pytest.mark.parametrize("arg, expected", [ @@ -44,7 +44,7 @@ def test_instant_with_a_valid_argument(arg, expected): @pytest.mark.parametrize("arg, error", [ ["1000-0", ValueError], ["1000-0-0", ValueError], - ["1000-1", ValueError], + ["1000-1", ValueError], ["1000-1-1", ValueError], ["1", ValueError], ["a", ValueError], @@ -61,4 +61,4 @@ def test_instant_with_a_valid_argument(arg, expected): ]) def test_instant_with_an_invalid_argument(arg, error): with pytest.raises(error): - periods.instant(arg) \ No newline at end of file + periods.instant(arg) diff --git a/openfisca_core/periods/tests/helpers/test_instant_date.py b/openfisca_core/periods/tests/helpers/test_instant_date.py new file mode 100644 index 0000000000..bd1872b079 --- /dev/null +++ b/openfisca_core/periods/tests/helpers/test_instant_date.py @@ -0,0 +1,42 @@ +import datetime + +import pytest + +from openfisca_core import periods +from openfisca_core.periods import DateUnit, Instant, Period + + +def test_instant_date(): + assert periods.instant_date((2022, 1, 1)) == datetime.date(2022, 1, 1) + + +@pytest.mark.parametrize("arg, expected", [ + [None, None], + [Instant((1, 1, 1)), datetime.date(1, 1, 1)], + [Instant((4, 2, 29)), datetime.date(4, 2, 29)], + [(1, 1, 1), datetime.date(1, 1, 1)], + ]) +def test_instant_date_with_a_valid_argument(arg, expected): + assert periods.instant_date(arg) == expected + + +@pytest.mark.parametrize("arg, error", [ + [datetime.date(1, 1, 1), TypeError], + [Instant((-1, 1, 1)), ValueError], + [Instant((1, -1, 1)), ValueError], + [Instant((1, 13, -1)), ValueError], + [Instant((1, 1, -1)), ValueError], + [Instant((1, 1, 32)), ValueError], + [Instant((1, 2, 29)), ValueError], + [Instant(("1", 1, 1)), TypeError], + [Period((DateUnit.YEAR, Instant((1, 1, 1)), 1)), TypeError], + [1, TypeError], + ["1", TypeError], + [(), TypeError], + [(Instant((1, 1, 1)),), TypeError], + [(1,), TypeError], + [(1, 1), TypeError], + ]) +def test_instant_date_with_an_invalid_argument(arg, error): + with pytest.raises(error): + periods.instant_date(arg) From dd875adb4f8b073f9323dd148849e30866acd343 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Sat, 30 Jul 2022 16:37:19 +0200 Subject: [PATCH 09/70] Add tests to periods.key_period_size --- openfisca_core/periods/helpers.py | 9 +----- .../periods/tests/helpers/test_instant.py | 4 --- .../tests/helpers/test_instant_date.py | 4 --- .../tests/helpers/test_key_period_size.py | 32 +++++++++++++++++++ 4 files changed, 33 insertions(+), 16 deletions(-) create mode 100644 openfisca_core/periods/tests/helpers/test_key_period_size.py diff --git a/openfisca_core/periods/helpers.py b/openfisca_core/periods/helpers.py index bc098e4c8a..e70f64e75c 100644 --- a/openfisca_core/periods/helpers.py +++ b/openfisca_core/periods/helpers.py @@ -247,22 +247,15 @@ def key_period_size(period): Examples: >>> instant = Instant((2021, 9, 14)) + >>> period = Period(("day", instant, 1)) >>> key_period_size(period) '100_1' - >>> period = Period(("month", instant, 2)) - >>> key_period_size(period) - '200_2' - >>> period = Period(("year", instant, 3)) >>> key_period_size(period) '300_3' - >>> period = Period(("eternity", instant, 4)) - >>> key_period_size(period) - '400_4' - """ unit, start, size = period diff --git a/openfisca_core/periods/tests/helpers/test_instant.py b/openfisca_core/periods/tests/helpers/test_instant.py index c96a467f54..d05e39ef5c 100644 --- a/openfisca_core/periods/tests/helpers/test_instant.py +++ b/openfisca_core/periods/tests/helpers/test_instant.py @@ -6,10 +6,6 @@ from openfisca_core.periods import DateUnit, Instant, Period -def test_instant(): - assert periods.instant((2022, 1, 1)) == Instant((2022, 1, 1)) - - @pytest.mark.parametrize("arg, expected", [ [None, None], [datetime.date(1, 1, 1), Instant((1, 1, 1))], diff --git a/openfisca_core/periods/tests/helpers/test_instant_date.py b/openfisca_core/periods/tests/helpers/test_instant_date.py index bd1872b079..474628a76c 100644 --- a/openfisca_core/periods/tests/helpers/test_instant_date.py +++ b/openfisca_core/periods/tests/helpers/test_instant_date.py @@ -6,10 +6,6 @@ from openfisca_core.periods import DateUnit, Instant, Period -def test_instant_date(): - assert periods.instant_date((2022, 1, 1)) == datetime.date(2022, 1, 1) - - @pytest.mark.parametrize("arg, expected", [ [None, None], [Instant((1, 1, 1)), datetime.date(1, 1, 1)], diff --git a/openfisca_core/periods/tests/helpers/test_key_period_size.py b/openfisca_core/periods/tests/helpers/test_key_period_size.py new file mode 100644 index 0000000000..52f372f1dd --- /dev/null +++ b/openfisca_core/periods/tests/helpers/test_key_period_size.py @@ -0,0 +1,32 @@ +import datetime + +import pytest + +from openfisca_core import periods +from openfisca_core.periods import DateUnit, Instant, Period + + +@pytest.mark.parametrize("arg, expected", [ + [Period((DateUnit.DAY, Instant((1, 1, 1)), 365)), "100_365"], + [Period((DateUnit.MONTH, Instant((1, 1, 1)), 12)), "200_12"], + [Period((DateUnit.YEAR, Instant((1, 1, 1)), 2)), "300_2"], + [Period((DateUnit.ETERNITY, Instant((1, 1, 1)), 1)), "400_1"], + [(DateUnit.DAY, None, 1), "100_1"], + [(DateUnit.MONTH, None, -1000), "200_-1000"], + ]) +def test_key_period_size_with_a_valid_argument(arg, expected): + assert periods.key_period_size(arg) == expected + + +@pytest.mark.parametrize("arg, error", [ + [None, TypeError], + [Instant((1, 1, 1)), KeyError], + [1, TypeError], + ["1", ValueError], + ["111", KeyError], + [(), ValueError], + [(1, 1, 1), KeyError], + ]) +def test_key_period_size_with_an_invalid_argument(arg, error): + with pytest.raises(error): + periods.key_period_size(arg) From cd470f29291754e8a1a8c3808b90c481c3cc225d Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Sat, 30 Jul 2022 16:41:36 +0200 Subject: [PATCH 10/70] Add doctest to periods.unit_weights --- openfisca_core/periods/helpers.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/openfisca_core/periods/helpers.py b/openfisca_core/periods/helpers.py index e70f64e75c..0b6fe174d7 100644 --- a/openfisca_core/periods/helpers.py +++ b/openfisca_core/periods/helpers.py @@ -264,6 +264,14 @@ def key_period_size(period): def unit_weights(): + """Assigns weights to date units. + + Examples: + >>> unit_weights() + {: 100...} + + """ + return { config.DAY: 100, config.MONTH: 200, From faee825900c17306b57ea5ce9c5eb5075cbd627c Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Sat, 30 Jul 2022 16:43:01 +0200 Subject: [PATCH 11/70] Add doctest to periods.unit_weight --- openfisca_core/periods/helpers.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/openfisca_core/periods/helpers.py b/openfisca_core/periods/helpers.py index 0b6fe174d7..f305e6f393 100644 --- a/openfisca_core/periods/helpers.py +++ b/openfisca_core/periods/helpers.py @@ -281,4 +281,12 @@ def unit_weights(): def unit_weight(unit): + """Retrieves a specific date unit weight. + + Examples: + >>> unit_weight(DateUnit.DAY) + 100 + + """ + return unit_weights()[unit] From 9969c9ee8d93c1dc596bf01d0690565657861a4a Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Sat, 30 Jul 2022 16:43:52 +0200 Subject: [PATCH 12/70] Fix typo in doc --- openfisca_core/periods/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openfisca_core/periods/helpers.py b/openfisca_core/periods/helpers.py index f305e6f393..9979307e30 100644 --- a/openfisca_core/periods/helpers.py +++ b/openfisca_core/periods/helpers.py @@ -227,7 +227,7 @@ def raise_error(value): else: raise_error(value) - # reject ambiguous period such as month:2014 + # reject ambiguous periods such as month:2014 if unit_weight(base_period.unit) > unit_weight(unit): raise_error(value) From 98329828cf6ff2c861c74521c946b715cce47f76 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Sat, 30 Jul 2022 16:51:55 +0200 Subject: [PATCH 13/70] Extract raise error from period builder --- openfisca_core/periods/helpers.py | 57 ++++++++++++------- .../periods/tests/helpers/test_instant.py | 6 +- .../tests/helpers/test_instant_date.py | 4 +- .../tests/helpers/test_key_period_size.py | 16 +++--- 4 files changed, 47 insertions(+), 36 deletions(-) diff --git a/openfisca_core/periods/helpers.py b/openfisca_core/periods/helpers.py index 9979307e30..664616a83c 100644 --- a/openfisca_core/periods/helpers.py +++ b/openfisca_core/periods/helpers.py @@ -11,7 +11,7 @@ def N_(message): def instant(instant): - """Return a new instant, aka a triple of integers (year, month, day). + """Build a new instant, aka a triple of integers (year, month, day). Args: instant: An ``instant-like`` object. @@ -102,7 +102,7 @@ def instant_date(instant): def period(value): - """Returns a new period, aka a triple (unit, start_instant, size). + """Build a new period, aka a triple (unit, start_instant, size). Args: value: A ``period-like`` object. @@ -153,10 +153,13 @@ def period(value): return Period((config.DAY, value, 1)) def parse_simple_period(value): - """Parses simple periods respecting the ISO format. + """Parse simple periods respecting the ISO format. Such as 2012 or 2015-03. + Examples: + >>> 1 > 2 + """ try: @@ -176,14 +179,6 @@ def parse_simple_period(value): else: return Period((config.YEAR, Instant((date.year, date.month, 1)), 1)) - def raise_error(value): - message = os.linesep.join([ - "Expected a period (eg. '2017', '2017-01', '2017-01-01', ...); got: '{}'.".format(value), - "Learn more about legal period formats in OpenFisca:", - "." - ]) - raise ValueError(message) - if value == 'ETERNITY' or value == config.ETERNITY: return Period(('eternity', instant(datetime.date.min), float("inf"))) @@ -191,7 +186,7 @@ def raise_error(value): if isinstance(value, int): return Period((config.YEAR, Instant((value, 1, 1)), 1)) if not isinstance(value, str): - raise_error(value) + _raise_error(value) # try to parse as a simple period period = parse_simple_period(value) @@ -200,19 +195,19 @@ def raise_error(value): # complex period must have a ':' in their strings if ":" not in value: - raise_error(value) + _raise_error(value) components = value.split(':') # left-most component must be a valid unit unit = components[0] if unit not in (config.DAY, config.MONTH, config.YEAR): - raise_error(value) + _raise_error(value) # middle component must be a valid iso period base_period = parse_simple_period(components[1]) if not base_period: - raise_error(value) + _raise_error(value) # period like year:2015-03 have a size of 1 if len(components) == 2: @@ -222,20 +217,38 @@ def raise_error(value): try: size = int(components[2]) except ValueError: - raise_error(value) + _raise_error(value) # if there is more than 2 ":" in the string, the period is invalid else: - raise_error(value) + _raise_error(value) # reject ambiguous periods such as month:2014 if unit_weight(base_period.unit) > unit_weight(unit): - raise_error(value) + _raise_error(value) return Period((unit, base_period.start, size)) +def _raise_error(value): + """Raise an error. + + Examples: + >>> _raise_error("Oi mate!") + Traceback (most recent call last): + ValueError: Expected a period (eg. '2017', '2017-01', '2017-01-01', ...); got: 'Oi mate!'. + + """ + + message = os.linesep.join([ + "Expected a period (eg. '2017', '2017-01', '2017-01-01', ...); got: '{}'.".format(value), + "Learn more about legal period formats in OpenFisca:", + "." + ]) + raise ValueError(message) + + def key_period_size(period): - """Defines a key in order to sort periods by length. + """Define a key in order to sort periods by length. It uses two aspects: first, ``unit``, then, ``size``. @@ -264,11 +277,11 @@ def key_period_size(period): def unit_weights(): - """Assigns weights to date units. + """Assign weights to date units. Examples: >>> unit_weights() - {: 100...} + {'day': 100, ...} """ @@ -284,7 +297,7 @@ def unit_weight(unit): """Retrieves a specific date unit weight. Examples: - >>> unit_weight(DateUnit.DAY) + >>> unit_weight("day") 100 """ diff --git a/openfisca_core/periods/tests/helpers/test_instant.py b/openfisca_core/periods/tests/helpers/test_instant.py index d05e39ef5c..d147fcdbf9 100644 --- a/openfisca_core/periods/tests/helpers/test_instant.py +++ b/openfisca_core/periods/tests/helpers/test_instant.py @@ -3,14 +3,14 @@ import pytest from openfisca_core import periods -from openfisca_core.periods import DateUnit, Instant, Period +from openfisca_core.periods import Instant, Period @pytest.mark.parametrize("arg, expected", [ [None, None], [datetime.date(1, 1, 1), Instant((1, 1, 1))], [Instant((1, 1, 1)), Instant((1, 1, 1))], - [Period((DateUnit.DAY, Instant((1, 1, 1)), 365)), Instant((1, 1, 1))], + [Period((periods.DAY, Instant((1, 1, 1)), 365)), Instant((1, 1, 1))], [-1, Instant((-1, 1, 1))], [0, Instant((0, 1, 1))], [1, Instant((1, 1, 1))], @@ -23,7 +23,7 @@ [(None, None, None), Instant((None, None, None))], [(datetime.date(1, 1, 1),), Instant((datetime.date(1, 1, 1), 1, 1))], [(Instant((1, 1, 1)),), Instant((Instant((1, 1, 1)), 1, 1))], - [(Period((DateUnit.DAY, Instant((1, 1, 1)), 365)),), Instant((Period((DateUnit.DAY, Instant((1, 1, 1)), 365)), 1, 1))], + [(Period((periods.DAY, Instant((1, 1, 1)), 365)),), Instant((Period((periods.DAY, Instant((1, 1, 1)), 365)), 1, 1))], [(-1,), Instant((-1, 1, 1))], [(-1, -1), Instant((-1, -1, 1))], [(-1, -1, -1), Instant((-1, -1, -1))], diff --git a/openfisca_core/periods/tests/helpers/test_instant_date.py b/openfisca_core/periods/tests/helpers/test_instant_date.py index 474628a76c..22bd459d0f 100644 --- a/openfisca_core/periods/tests/helpers/test_instant_date.py +++ b/openfisca_core/periods/tests/helpers/test_instant_date.py @@ -3,7 +3,7 @@ import pytest from openfisca_core import periods -from openfisca_core.periods import DateUnit, Instant, Period +from openfisca_core.periods import Instant, Period @pytest.mark.parametrize("arg, expected", [ @@ -25,7 +25,7 @@ def test_instant_date_with_a_valid_argument(arg, expected): [Instant((1, 1, 32)), ValueError], [Instant((1, 2, 29)), ValueError], [Instant(("1", 1, 1)), TypeError], - [Period((DateUnit.YEAR, Instant((1, 1, 1)), 1)), TypeError], + [Period((periods.YEAR, Instant((1, 1, 1)), 1)), TypeError], [1, TypeError], ["1", TypeError], [(), TypeError], diff --git a/openfisca_core/periods/tests/helpers/test_key_period_size.py b/openfisca_core/periods/tests/helpers/test_key_period_size.py index 52f372f1dd..6f8acc17c9 100644 --- a/openfisca_core/periods/tests/helpers/test_key_period_size.py +++ b/openfisca_core/periods/tests/helpers/test_key_period_size.py @@ -1,18 +1,16 @@ -import datetime - import pytest from openfisca_core import periods -from openfisca_core.periods import DateUnit, Instant, Period +from openfisca_core.periods import Instant, Period @pytest.mark.parametrize("arg, expected", [ - [Period((DateUnit.DAY, Instant((1, 1, 1)), 365)), "100_365"], - [Period((DateUnit.MONTH, Instant((1, 1, 1)), 12)), "200_12"], - [Period((DateUnit.YEAR, Instant((1, 1, 1)), 2)), "300_2"], - [Period((DateUnit.ETERNITY, Instant((1, 1, 1)), 1)), "400_1"], - [(DateUnit.DAY, None, 1), "100_1"], - [(DateUnit.MONTH, None, -1000), "200_-1000"], + [Period((periods.DAY, Instant((1, 1, 1)), 365)), "100_365"], + [Period((periods.MONTH, Instant((1, 1, 1)), 12)), "200_12"], + [Period((periods.YEAR, Instant((1, 1, 1)), 2)), "300_2"], + [Period((periods.ETERNITY, Instant((1, 1, 1)), 1)), "400_1"], + [(periods.DAY, None, 1), "100_1"], + [(periods.MONTH, None, -1000), "200_-1000"], ]) def test_key_period_size_with_a_valid_argument(arg, expected): assert periods.key_period_size(arg) == expected From b33bf2d794bd2290f0202c04a91b1d73b9bd0a19 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Sat, 30 Jul 2022 17:40:45 +0200 Subject: [PATCH 14/70] Extract parse simple period from builder --- openfisca_core/periods/helpers.py | 79 +++++++++++-------- openfisca_core/periods/period_.py | 8 +- .../helpers/test__parse_simple_period.py | 19 +++++ .../periods/tests/helpers/test_instant.py | 5 ++ .../tests/helpers/test_instant_date.py | 8 +- .../tests/helpers/test_key_period_size.py | 14 ---- .../periods/tests/helpers/test_period.py | 0 7 files changed, 73 insertions(+), 60 deletions(-) create mode 100644 openfisca_core/periods/tests/helpers/test__parse_simple_period.py create mode 100644 openfisca_core/periods/tests/helpers/test_period.py diff --git a/openfisca_core/periods/helpers.py b/openfisca_core/periods/helpers.py index 664616a83c..a9735d6cb3 100644 --- a/openfisca_core/periods/helpers.py +++ b/openfisca_core/periods/helpers.py @@ -1,5 +1,6 @@ import datetime import os +from typing import Dict, NoReturn, Optional from . import config from .instant_ import Instant @@ -10,7 +11,7 @@ def N_(message): return message -def instant(instant): +def instant(instant) -> Optional[Instant]: """Build a new instant, aka a triple of integers (year, month, day). Args: @@ -77,7 +78,7 @@ def instant(instant): return Instant(instant) -def instant_date(instant): +def instant_date(instant: Optional[Instant]) -> Optional[datetime.date]: """Returns the date representation of an :class:`.Instant`. Args: @@ -152,33 +153,6 @@ def period(value): if isinstance(value, Instant): return Period((config.DAY, value, 1)) - def parse_simple_period(value): - """Parse simple periods respecting the ISO format. - - Such as 2012 or 2015-03. - - Examples: - >>> 1 > 2 - - """ - - try: - date = datetime.datetime.strptime(value, '%Y') - except ValueError: - try: - date = datetime.datetime.strptime(value, '%Y-%m') - except ValueError: - try: - date = datetime.datetime.strptime(value, '%Y-%m-%d') - except ValueError: - return None - else: - return Period((config.DAY, Instant((date.year, date.month, date.day)), 1)) - else: - return Period((config.MONTH, Instant((date.year, date.month, 1)), 1)) - else: - return Period((config.YEAR, Instant((date.year, date.month, 1)), 1)) - if value == 'ETERNITY' or value == config.ETERNITY: return Period(('eternity', instant(datetime.date.min), float("inf"))) @@ -189,7 +163,7 @@ def parse_simple_period(value): _raise_error(value) # try to parse as a simple period - period = parse_simple_period(value) + period = _parse_simple_period(value) if period is not None: return period @@ -205,7 +179,7 @@ def parse_simple_period(value): _raise_error(value) # middle component must be a valid iso period - base_period = parse_simple_period(components[1]) + base_period = _parse_simple_period(components[1]) if not base_period: _raise_error(value) @@ -229,7 +203,42 @@ def parse_simple_period(value): return Period((unit, base_period.start, size)) -def _raise_error(value): +def _parse_simple_period(value: str) -> Optional[Period]: + """Parse simple periods respecting the ISO format. + + Such as "2012" or "2015-03". + + Examples: + >>> _parse_simple_period("2022") + Period(('year', Instant((2022, 1, 1)), 1)) + + >>> _parse_simple_period("2022-02") + Period(('month', Instant((2022, 2, 1)), 1)) + + >>> _parse_simple_period("2022-02-13") + Period(('day', Instant((2022, 2, 13)), 1)) + + """ + + try: + date = datetime.datetime.strptime(value, '%Y') + except ValueError: + try: + date = datetime.datetime.strptime(value, '%Y-%m') + except ValueError: + try: + date = datetime.datetime.strptime(value, '%Y-%m-%d') + except ValueError: + return None + else: + return Period((config.DAY, Instant((date.year, date.month, date.day)), 1)) + else: + return Period((config.MONTH, Instant((date.year, date.month, 1)), 1)) + else: + return Period((config.YEAR, Instant((date.year, date.month, 1)), 1)) + + +def _raise_error(value: str) -> NoReturn: """Raise an error. Examples: @@ -247,7 +256,7 @@ def _raise_error(value): raise ValueError(message) -def key_period_size(period): +def key_period_size(period: Period) -> str: """Define a key in order to sort periods by length. It uses two aspects: first, ``unit``, then, ``size``. @@ -276,7 +285,7 @@ def key_period_size(period): return '{}_{}'.format(unit_weight(unit), size) -def unit_weights(): +def unit_weights() -> Dict[str, int]: """Assign weights to date units. Examples: @@ -293,7 +302,7 @@ def unit_weights(): } -def unit_weight(unit): +def unit_weight(unit: str) -> int: """Retrieves a specific date unit weight. Examples: diff --git a/openfisca_core/periods/period_.py b/openfisca_core/periods/period_.py index 1c9942c681..707bc43c0e 100644 --- a/openfisca_core/periods/period_.py +++ b/openfisca_core/periods/period_.py @@ -38,7 +38,7 @@ class Period(tuple): >>> dict([period, instant]) Traceback (most recent call last): - ValueError: dictionary update sequence element #0 has length 3; 2 is required + ValueError: dictionary update sequence element #0 has length 3... >>> list(period) ['year', Instant((2021, 9, 1)), 3] @@ -238,16 +238,16 @@ def intersection(self, start, stop): )) def get_subperiods(self, unit): - """Return the list of all the periods of unit ``unit`` contained in self. + """Return the list of periods of unit ``unit`` contained in self. Examples: >>> period = Period(("year", Instant((2021, 1, 1)), 1)) >>> period.get_subperiods("month") - [Period(('month', Instant((2021, 1, 1)), 1)), ...2021, 12, 1)), 1))] + [Period(('month', Instant((2021, 1, 1)), 1)),...2021, 12, 1)), 1))] >>> period = Period(("year", Instant((2021, 1, 1)), 2)) >>> period.get_subperiods("year") - [Period(('year', Instant((2021, 1, 1)), 1)), ...((2022, 1, 1)), 1))] + [Period(('year', Instant((2021, 1, 1)), 1)),...((2022, 1, 1)), 1))] """ diff --git a/openfisca_core/periods/tests/helpers/test__parse_simple_period.py b/openfisca_core/periods/tests/helpers/test__parse_simple_period.py new file mode 100644 index 0000000000..081d795e85 --- /dev/null +++ b/openfisca_core/periods/tests/helpers/test__parse_simple_period.py @@ -0,0 +1,19 @@ +import pytest + +from openfisca_core import periods +from openfisca_core.periods import Instant, Period, helpers + + +@pytest.mark.parametrize("arg, expected", [ + ["1", None], + ["999", None], + ["1000", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], + ["1000-1", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], + ["1000-01", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], + ["1000-1-1", Period((periods.DAY, Instant((1000, 1, 1)), 1))], + ["1000-01-1", Period((periods.DAY, Instant((1000, 1, 1)), 1))], + ["1000-01-01", Period((periods.DAY, Instant((1000, 1, 1)), 1))], + ["1000-01-99", None], + ]) +def test__parse_simple_period_with_a_valid_argument(arg, expected): + assert helpers._parse_simple_period(arg) == expected diff --git a/openfisca_core/periods/tests/helpers/test_instant.py b/openfisca_core/periods/tests/helpers/test_instant.py index d147fcdbf9..dd6002c73f 100644 --- a/openfisca_core/periods/tests/helpers/test_instant.py +++ b/openfisca_core/periods/tests/helpers/test_instant.py @@ -17,6 +17,7 @@ [999, Instant((999, 1, 1))], [1000, Instant((1000, 1, 1))], ["1000", Instant((1000, 1, 1))], + ["1000-01", Instant((1000, 1, 1))], ["1000-01-01", Instant((1000, 1, 1))], [(None,), Instant((None, 1, 1))], [(None, None), Instant((None, None, 1))], @@ -38,12 +39,16 @@ def test_instant_with_a_valid_argument(arg, expected): @pytest.mark.parametrize("arg, error", [ + [periods.YEAR, ValueError], + [periods.ETERNITY, ValueError], ["1000-0", ValueError], ["1000-0-0", ValueError], ["1000-1", ValueError], ["1000-1-1", ValueError], ["1", ValueError], ["a", ValueError], + ["year", ValueError], + ["eternity", ValueError], ["999", ValueError], ["1:1000-01-01", ValueError], ["a:1000-01-01", ValueError], diff --git a/openfisca_core/periods/tests/helpers/test_instant_date.py b/openfisca_core/periods/tests/helpers/test_instant_date.py index 22bd459d0f..722728a6e4 100644 --- a/openfisca_core/periods/tests/helpers/test_instant_date.py +++ b/openfisca_core/periods/tests/helpers/test_instant_date.py @@ -3,7 +3,7 @@ import pytest from openfisca_core import periods -from openfisca_core.periods import Instant, Period +from openfisca_core.periods import Instant @pytest.mark.parametrize("arg, expected", [ @@ -17,7 +17,6 @@ def test_instant_date_with_a_valid_argument(arg, expected): @pytest.mark.parametrize("arg, error", [ - [datetime.date(1, 1, 1), TypeError], [Instant((-1, 1, 1)), ValueError], [Instant((1, -1, 1)), ValueError], [Instant((1, 13, -1)), ValueError], @@ -25,11 +24,6 @@ def test_instant_date_with_a_valid_argument(arg, expected): [Instant((1, 1, 32)), ValueError], [Instant((1, 2, 29)), ValueError], [Instant(("1", 1, 1)), TypeError], - [Period((periods.YEAR, Instant((1, 1, 1)), 1)), TypeError], - [1, TypeError], - ["1", TypeError], - [(), TypeError], - [(Instant((1, 1, 1)),), TypeError], [(1,), TypeError], [(1, 1), TypeError], ]) diff --git a/openfisca_core/periods/tests/helpers/test_key_period_size.py b/openfisca_core/periods/tests/helpers/test_key_period_size.py index 6f8acc17c9..1094d4e42e 100644 --- a/openfisca_core/periods/tests/helpers/test_key_period_size.py +++ b/openfisca_core/periods/tests/helpers/test_key_period_size.py @@ -14,17 +14,3 @@ ]) def test_key_period_size_with_a_valid_argument(arg, expected): assert periods.key_period_size(arg) == expected - - -@pytest.mark.parametrize("arg, error", [ - [None, TypeError], - [Instant((1, 1, 1)), KeyError], - [1, TypeError], - ["1", ValueError], - ["111", KeyError], - [(), ValueError], - [(1, 1, 1), KeyError], - ]) -def test_key_period_size_with_an_invalid_argument(arg, error): - with pytest.raises(error): - periods.key_period_size(arg) diff --git a/openfisca_core/periods/tests/helpers/test_period.py b/openfisca_core/periods/tests/helpers/test_period.py new file mode 100644 index 0000000000..e69de29bb2 From ef06cebd929a6b643f92420778262c2080010c92 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Sat, 30 Jul 2022 18:16:21 +0200 Subject: [PATCH 15/70] Add tests to periods.period --- openfisca_core/periods/helpers.py | 2 +- .../periods/tests/helpers/test_period.py | 69 +++++++++++++++++++ 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/openfisca_core/periods/helpers.py b/openfisca_core/periods/helpers.py index a9735d6cb3..2002774aa1 100644 --- a/openfisca_core/periods/helpers.py +++ b/openfisca_core/periods/helpers.py @@ -102,7 +102,7 @@ def instant_date(instant: Optional[Instant]) -> Optional[datetime.date]: return instant_date -def period(value): +def period(value) -> Period: """Build a new period, aka a triple (unit, start_instant, size). Args: diff --git a/openfisca_core/periods/tests/helpers/test_period.py b/openfisca_core/periods/tests/helpers/test_period.py index e69de29bb2..4df00a9a18 100644 --- a/openfisca_core/periods/tests/helpers/test_period.py +++ b/openfisca_core/periods/tests/helpers/test_period.py @@ -0,0 +1,69 @@ +import datetime + +import pytest + +from openfisca_core import periods +from openfisca_core.periods import Instant, Period + + +@pytest.mark.parametrize("arg, expected", [ + ["eternity", Period((periods.ETERNITY, Instant((1, 1, 1)), float("inf")))], + ["ETERNITY", Period((periods.ETERNITY, Instant((1, 1, 1)), float("inf")))], + [periods.ETERNITY, Period((periods.ETERNITY, Instant((1, 1, 1)), float("inf")))], + [Instant((1, 1, 1)), Period((periods.DAY, Instant((1, 1, 1)), 1))], + [Period((periods.DAY, Instant((1, 1, 1)), 365)), Period((periods.DAY, Instant((1, 1, 1)), 365))], + [-1, Period((periods.YEAR, Instant((-1, 1, 1)), 1))], + [0, Period((periods.YEAR, Instant((0, 1, 1)), 1))], + [1, Period((periods.YEAR, Instant((1, 1, 1)), 1))], + [999, Period((periods.YEAR, Instant((999, 1, 1)), 1))], + [1000, Period((periods.YEAR, Instant((1000, 1, 1)), 1))], + ["1000", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], + ["1000-1", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], + ["1000-1-1", Period((periods.DAY, Instant((1000, 1, 1)), 1))], + ["1000-01", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], + ["1000-01-01", Period((periods.DAY, Instant((1000, 1, 1)), 1))], + ["year:1000-01-01", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], + ["year:1000-01-01:1", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], + ["year:1000-01-01:3", Period((periods.YEAR, Instant((1000, 1, 1)), 3))], + ]) +def test_instant_with_a_valid_argument(arg, expected): + assert periods.period(arg) == expected + + +@pytest.mark.parametrize("arg, error", [ + [None, ValueError], + [periods.YEAR, ValueError], + [datetime.date(1, 1, 1), ValueError], + ["1000-0", ValueError], + ["1000-0-0", ValueError], + ["1", ValueError], + ["a", ValueError], + ["year", ValueError], + ["999", ValueError], + ["1:1000-01-01", ValueError], + ["a:1000-01-01", ValueError], + ["1000-01-01:a", ValueError], + ["1000-01-01:1", ValueError], + [(), ValueError], + [(None,), ValueError], + [(None, None), ValueError], + [(None, None, None), ValueError], + [(None, None, None, None), ValueError], + [(datetime.date(1, 1, 1),), ValueError], + [(Instant((1, 1, 1)),), ValueError], + [(Period((periods.DAY, Instant((1, 1, 1)), 365)),), ValueError], + [(1,), ValueError], + [(1, 1), ValueError], + [(1, 1, 1), ValueError], + [(-1,), ValueError], + [(-1, -1), ValueError], + [(-1, -1, -1), ValueError], + [("-1",), ValueError], + [("-1", "-1"), ValueError], + [("-1", "-1", "-1"), ValueError], + [("1-1",), ValueError], + [("1-1-1",), ValueError], + ]) +def test_instant_with_an_invalid_argument(arg, error): + with pytest.raises(error): + periods.period(arg) From fc93df4cf55957d9dfab15952621d13e33bdaddf Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Sat, 30 Jul 2022 18:44:02 +0200 Subject: [PATCH 16/70] Refactor period's str year tests --- .../periods/tests/period/__init__.py | 0 .../periods/tests/period/test_str.py | 25 +++++++++++++++++++ tests/core/test_periods.py | 20 --------------- 3 files changed, 25 insertions(+), 20 deletions(-) create mode 100644 openfisca_core/periods/tests/period/__init__.py create mode 100644 openfisca_core/periods/tests/period/test_str.py diff --git a/openfisca_core/periods/tests/period/__init__.py b/openfisca_core/periods/tests/period/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openfisca_core/periods/tests/period/test_str.py b/openfisca_core/periods/tests/period/test_str.py new file mode 100644 index 0000000000..45a2e588cb --- /dev/null +++ b/openfisca_core/periods/tests/period/test_str.py @@ -0,0 +1,25 @@ +import pytest + +from openfisca_core.periods import DateUnit, Instant, Period + + +@pytest.fixture +def first_jan(): + return Instant((2022, 1, 1)) + + +@pytest.fixture +def first_march(): + return Instant((2022, 3, 1)) + + +@pytest.mark.parametrize("date_unit, instant, size, expected", [ + [DateUnit.YEAR, Instant((2022, 1, 1)), 1, "2022"], + [DateUnit.MONTH, Instant((2022, 1, 1)), 12, "2022"], + [DateUnit.YEAR, Instant((2022, 3, 1)), 1, "year:2022-03"], + [DateUnit.MONTH, Instant((2022, 3, 1)), 12, "year:2022-03"], + [DateUnit.YEAR, Instant((2022, 1, 1)), 3, "year:2022:3"], + [DateUnit.YEAR, Instant((2022, 1, 3)), 3, "year:2022:3"], + ]) +def test_str_with_years(date_unit, instant, size, expected): + assert str(Period((date_unit, instant, size))) == expected \ No newline at end of file diff --git a/tests/core/test_periods.py b/tests/core/test_periods.py index 2c125d527c..9e23b0d782 100644 --- a/tests/core/test_periods.py +++ b/tests/core/test_periods.py @@ -14,26 +14,6 @@ ''' -# Years - -def test_year(): - assert str(Period((YEAR, first_jan, 1))) == '2014' - - -def test_12_months_is_a_year(): - assert str(Period((MONTH, first_jan, 12))) == '2014' - - -def test_rolling_year(): - assert str(Period((MONTH, first_march, 12))) == 'year:2014-03' - assert str(Period((YEAR, first_march, 1))) == 'year:2014-03' - - -def test_several_years(): - assert str(Period((YEAR, first_jan, 3))) == 'year:2014:3' - assert str(Period((YEAR, first_march, 3))) == 'year:2014-03:3' - - # Months def test_month(): From 51cff30d247be806f7eb1bce53d0f8c03919e8b0 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Sat, 30 Jul 2022 18:46:23 +0200 Subject: [PATCH 17/70] Refactor period's str month tests --- .../periods/tests/period/test_str.py | 19 +++++++++---------- tests/core/test_periods.py | 7 ------- 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/openfisca_core/periods/tests/period/test_str.py b/openfisca_core/periods/tests/period/test_str.py index 45a2e588cb..f96102547b 100644 --- a/openfisca_core/periods/tests/period/test_str.py +++ b/openfisca_core/periods/tests/period/test_str.py @@ -3,16 +3,6 @@ from openfisca_core.periods import DateUnit, Instant, Period -@pytest.fixture -def first_jan(): - return Instant((2022, 1, 1)) - - -@pytest.fixture -def first_march(): - return Instant((2022, 3, 1)) - - @pytest.mark.parametrize("date_unit, instant, size, expected", [ [DateUnit.YEAR, Instant((2022, 1, 1)), 1, "2022"], [DateUnit.MONTH, Instant((2022, 1, 1)), 12, "2022"], @@ -22,4 +12,13 @@ def first_march(): [DateUnit.YEAR, Instant((2022, 1, 3)), 3, "year:2022:3"], ]) def test_str_with_years(date_unit, instant, size, expected): + assert str(Period((date_unit, instant, size))) == expected + + +@pytest.mark.parametrize("date_unit, instant, size, expected", [ + [DateUnit.MONTH, Instant((2022, 1, 1)), 1, "2022-01"], + [DateUnit.MONTH, Instant((2022, 1, 1)), 3, "month:2022-01:3"], + [DateUnit.MONTH, Instant((2022, 3, 1)), 3, "month:2022-03:3"], + ]) +def test_str_with_months(date_unit, instant, size, expected): assert str(Period((date_unit, instant, size))) == expected \ No newline at end of file diff --git a/tests/core/test_periods.py b/tests/core/test_periods.py index 9e23b0d782..4ab5a3f91f 100644 --- a/tests/core/test_periods.py +++ b/tests/core/test_periods.py @@ -16,13 +16,6 @@ # Months -def test_month(): - assert str(Period((MONTH, first_jan, 1))) == '2014-01' - - -def test_several_months(): - assert str(Period((MONTH, first_jan, 3))) == 'month:2014-01:3' - assert str(Period((MONTH, first_march, 3))) == 'month:2014-03:3' # Days From 5c3a163b392f14305a11f1a13418a0b2c1074bf6 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Sat, 30 Jul 2022 18:48:23 +0200 Subject: [PATCH 18/70] Refactor period's str day tests --- .../periods/tests/period/test_str.py | 9 ++++++++ tests/core/test_periods.py | 21 ------------------- 2 files changed, 9 insertions(+), 21 deletions(-) diff --git a/openfisca_core/periods/tests/period/test_str.py b/openfisca_core/periods/tests/period/test_str.py index f96102547b..47ea3c390d 100644 --- a/openfisca_core/periods/tests/period/test_str.py +++ b/openfisca_core/periods/tests/period/test_str.py @@ -21,4 +21,13 @@ def test_str_with_years(date_unit, instant, size, expected): [DateUnit.MONTH, Instant((2022, 3, 1)), 3, "month:2022-03:3"], ]) def test_str_with_months(date_unit, instant, size, expected): + assert str(Period((date_unit, instant, size))) == expected + + +@pytest.mark.parametrize("date_unit, instant, size, expected", [ + [DateUnit.DAY, Instant((2022, 1, 1)), 1, "2022-01-01"], + [DateUnit.DAY, Instant((2022, 1, 1)), 3, "day:2022-01-01:3"], + [DateUnit.DAY, Instant((2022, 3, 1)), 3, "day:2022-03-01:3"], + ]) +def test_str_with_days(date_unit, instant, size, expected): assert str(Period((date_unit, instant, size))) == expected \ No newline at end of file diff --git a/tests/core/test_periods.py b/tests/core/test_periods.py index 4ab5a3f91f..92c8b49022 100644 --- a/tests/core/test_periods.py +++ b/tests/core/test_periods.py @@ -9,31 +9,10 @@ first_march = Instant((2014, 3, 1)) -''' -Test Period -> String -''' - - -# Months - - - -# Days - -def test_day(): - assert str(Period((DAY, first_jan, 1))) == '2014-01-01' - - -def test_several_days(): - assert str(Period((DAY, first_jan, 3))) == 'day:2014-01-01:3' - assert str(Period((DAY, first_march, 3))) == 'day:2014-03-01:3' - - ''' Test String -> Period ''' - # Years def test_parsing_year(): From 2304eb497495d5abec6aa03c9b1645f988578ee5 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Sat, 30 Jul 2022 18:54:46 +0200 Subject: [PATCH 19/70] Remove redundant examples --- openfisca_core/periods/period_.py | 36 ++----------------- .../periods/tests/period/test_str.py | 2 +- tests/core/test_periods.py | 1 + 3 files changed, 4 insertions(+), 35 deletions(-) diff --git a/openfisca_core/periods/period_.py b/openfisca_core/periods/period_.py index 707bc43c0e..7411d9f72c 100644 --- a/openfisca_core/periods/period_.py +++ b/openfisca_core/periods/period_.py @@ -121,42 +121,10 @@ class Period(tuple): """ - def __repr__(self): + def __repr__(self) -> str: return '{}({})'.format(self.__class__.__name__, super(Period, self).__repr__()) - def __str__(self): - """Transform period to a string. - - Examples: - >>> str(Period(("year", Instant((2021, 1, 1)), 1))) - '2021' - - >>> str(Period(("year", Instant((2021, 2, 1)), 1))) - 'year:2021-02' - - >>> str(Period(("month", Instant((2021, 2, 1)), 1))) - '2021-02' - - >>> str(Period(("year", Instant((2021, 1, 1)), 2))) - 'year:2021:2' - - >>> str(Period(("month", Instant((2021, 1, 1)), 2))) - 'month:2021-01:2' - - >>> str(Period(("month", Instant((2021, 1, 1)), 12))) - '2021' - - >>> str(Period(("year", Instant((2021, 3, 1)), 2))) - 'year:2021-03:2' - - >>> str(Period(("month", Instant((2021, 3, 1)), 2))) - 'month:2021-03:2' - - >>> str(Period(("month", Instant((2021, 3, 1)), 12))) - 'year:2021-03' - - """ - + def __str__(self) -> str: unit, start_instant, size = self if unit == config.ETERNITY: return 'ETERNITY' diff --git a/openfisca_core/periods/tests/period/test_str.py b/openfisca_core/periods/tests/period/test_str.py index 47ea3c390d..874a8405ab 100644 --- a/openfisca_core/periods/tests/period/test_str.py +++ b/openfisca_core/periods/tests/period/test_str.py @@ -30,4 +30,4 @@ def test_str_with_months(date_unit, instant, size, expected): [DateUnit.DAY, Instant((2022, 3, 1)), 3, "day:2022-03-01:3"], ]) def test_str_with_days(date_unit, instant, size, expected): - assert str(Period((date_unit, instant, size))) == expected \ No newline at end of file + assert str(Period((date_unit, instant, size))) == expected diff --git a/tests/core/test_periods.py b/tests/core/test_periods.py index 92c8b49022..58fffd7919 100644 --- a/tests/core/test_periods.py +++ b/tests/core/test_periods.py @@ -15,6 +15,7 @@ # Years + def test_parsing_year(): assert period('2014') == Period((YEAR, first_jan, 1)) From 521068f13e0c374091f78e5cba021a35bee3c257 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Sat, 30 Jul 2022 19:35:31 +0200 Subject: [PATCH 20/70] Redistribute tests --- openfisca_core/periods/period_.py | 1 - .../periods/tests/helpers/test_instant.py | 2 + .../periods/tests/helpers/test_period.py | 35 ++++- .../periods/tests/period/test_size_in_days.py | 20 +++ .../periods/tests/period/test_str.py | 27 ++-- tests/core/test_periods.py | 132 +----------------- 6 files changed, 68 insertions(+), 149 deletions(-) create mode 100644 openfisca_core/periods/tests/period/test_size_in_days.py diff --git a/openfisca_core/periods/period_.py b/openfisca_core/periods/period_.py index 7411d9f72c..d343e3df05 100644 --- a/openfisca_core/periods/period_.py +++ b/openfisca_core/periods/period_.py @@ -162,7 +162,6 @@ def date(self): @property def days(self): """Count the number of days in period.""" - return (self.stop.date - self.start.date).days + 1 def intersection(self, start, stop): diff --git a/openfisca_core/periods/tests/helpers/test_instant.py b/openfisca_core/periods/tests/helpers/test_instant.py index dd6002c73f..c58c5897f2 100644 --- a/openfisca_core/periods/tests/helpers/test_instant.py +++ b/openfisca_core/periods/tests/helpers/test_instant.py @@ -58,6 +58,8 @@ def test_instant_with_a_valid_argument(arg, expected): ["1000-01-01:a", ValueError], ["1000-01-01:1", ValueError], [(), AssertionError], + [{}, AssertionError], + ["", ValueError], [(None, None, None, None), AssertionError], ]) def test_instant_with_an_invalid_argument(arg, error): diff --git a/openfisca_core/periods/tests/helpers/test_period.py b/openfisca_core/periods/tests/helpers/test_period.py index 4df00a9a18..50cc59eae8 100644 --- a/openfisca_core/periods/tests/helpers/test_period.py +++ b/openfisca_core/periods/tests/helpers/test_period.py @@ -22,9 +22,24 @@ ["1000-1-1", Period((periods.DAY, Instant((1000, 1, 1)), 1))], ["1000-01", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], ["1000-01-01", Period((periods.DAY, Instant((1000, 1, 1)), 1))], + ["1004-02-29", Period((periods.DAY, Instant((1004, 2, 29)), 1))], + ["year:1000", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], + ["year:1000-01", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], ["year:1000-01-01", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], + ["year:1000:1", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], + ["year:1000-01:1", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], ["year:1000-01-01:1", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], - ["year:1000-01-01:3", Period((periods.YEAR, Instant((1000, 1, 1)), 3))], + ["year:1000:3", Period((periods.YEAR, Instant((1000, 1, 1)), 3))], + ["year:1000-01:3", Period((periods.YEAR, Instant((1000, 1, 1)), 3))], + ["month:1000-01-01:3", Period((periods.MONTH, Instant((1000, 1, 1)), 3))], + ["month:1000-01", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], + ["month:1000-01-01", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], + ["month:1000-01:1", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], + ["month:1000-01:3", Period((periods.MONTH, Instant((1000, 1, 1)), 3))], + ["month:1000-01-01:3", Period((periods.MONTH, Instant((1000, 1, 1)), 3))], + ["month:1000-01-01:3", Period((periods.MONTH, Instant((1000, 1, 1)), 3))], + ["day:1000-01-01", Period((periods.DAY, Instant((1000, 1, 1)), 1))], + ["day:1000-01-01:3", Period((periods.DAY, Instant((1000, 1, 1)), 3))], ]) def test_instant_with_a_valid_argument(arg, expected): assert periods.period(arg) == expected @@ -35,16 +50,28 @@ def test_instant_with_a_valid_argument(arg, expected): [periods.YEAR, ValueError], [datetime.date(1, 1, 1), ValueError], ["1000-0", ValueError], + ["1000-13", ValueError], ["1000-0-0", ValueError], + ["1000-1-0", ValueError], + ["1000-2-31", ValueError], ["1", ValueError], ["a", ValueError], ["year", ValueError], ["999", ValueError], - ["1:1000-01-01", ValueError], - ["a:1000-01-01", ValueError], - ["1000-01-01:a", ValueError], + ["1:1000", ValueError], + ["a:1000", ValueError], + ["month:1000", ValueError], + ["day:1000-01", ValueError], + ["1000:a", ValueError], + ["1000:1", ValueError], + ["1000-01:1", ValueError], ["1000-01-01:1", ValueError], + ["month:1000:1", ValueError], + ["day:1000:1", ValueError], + ["day:1000-01:1", ValueError], [(), ValueError], + [{}, ValueError], + ["", ValueError], [(None,), ValueError], [(None, None), ValueError], [(None, None, None), ValueError], diff --git a/openfisca_core/periods/tests/period/test_size_in_days.py b/openfisca_core/periods/tests/period/test_size_in_days.py new file mode 100644 index 0000000000..c68d5d82b0 --- /dev/null +++ b/openfisca_core/periods/tests/period/test_size_in_days.py @@ -0,0 +1,20 @@ +import pytest + +from openfisca_core import periods +from openfisca_core.periods import Instant, Period + + +@pytest.mark.parametrize("date_unit, instant, size, expected", [ + [periods.DAY, Instant((2022, 12, 31)), 1, 1], + [periods.DAY, Instant((2022, 12, 31)), 3, 3], + [periods.MONTH, Instant((2022, 12, 1)), 1, 31], + [periods.MONTH, Instant((2012, 2, 3)), 1, 29], + [periods.MONTH, Instant((2022, 1, 3)), 3, 31 + 28 + 31], + [periods.MONTH, Instant((2012, 1, 3)), 3, 31 + 29 + 31], + [periods.YEAR, Instant((2022, 12, 1)), 1, 365], + [periods.YEAR, Instant((2012, 1, 1)), 1, 366], + [periods.YEAR, Instant((2022, 1, 1)), 2, 730], + ]) +def test_day_size_in_days(date_unit, instant, size, expected): + period = Period((date_unit, instant, size)) + assert period.size_in_days == expected diff --git a/openfisca_core/periods/tests/period/test_str.py b/openfisca_core/periods/tests/period/test_str.py index 874a8405ab..4e0ba81446 100644 --- a/openfisca_core/periods/tests/period/test_str.py +++ b/openfisca_core/periods/tests/period/test_str.py @@ -1,33 +1,34 @@ import pytest -from openfisca_core.periods import DateUnit, Instant, Period +from openfisca_core import periods +from openfisca_core.periods import Instant, Period @pytest.mark.parametrize("date_unit, instant, size, expected", [ - [DateUnit.YEAR, Instant((2022, 1, 1)), 1, "2022"], - [DateUnit.MONTH, Instant((2022, 1, 1)), 12, "2022"], - [DateUnit.YEAR, Instant((2022, 3, 1)), 1, "year:2022-03"], - [DateUnit.MONTH, Instant((2022, 3, 1)), 12, "year:2022-03"], - [DateUnit.YEAR, Instant((2022, 1, 1)), 3, "year:2022:3"], - [DateUnit.YEAR, Instant((2022, 1, 3)), 3, "year:2022:3"], + [periods.YEAR, Instant((2022, 1, 1)), 1, "2022"], + [periods.MONTH, Instant((2022, 1, 1)), 12, "2022"], + [periods.YEAR, Instant((2022, 3, 1)), 1, "year:2022-03"], + [periods.MONTH, Instant((2022, 3, 1)), 12, "year:2022-03"], + [periods.YEAR, Instant((2022, 1, 1)), 3, "year:2022:3"], + [periods.YEAR, Instant((2022, 1, 3)), 3, "year:2022:3"], ]) def test_str_with_years(date_unit, instant, size, expected): assert str(Period((date_unit, instant, size))) == expected @pytest.mark.parametrize("date_unit, instant, size, expected", [ - [DateUnit.MONTH, Instant((2022, 1, 1)), 1, "2022-01"], - [DateUnit.MONTH, Instant((2022, 1, 1)), 3, "month:2022-01:3"], - [DateUnit.MONTH, Instant((2022, 3, 1)), 3, "month:2022-03:3"], + [periods.MONTH, Instant((2022, 1, 1)), 1, "2022-01"], + [periods.MONTH, Instant((2022, 1, 1)), 3, "month:2022-01:3"], + [periods.MONTH, Instant((2022, 3, 1)), 3, "month:2022-03:3"], ]) def test_str_with_months(date_unit, instant, size, expected): assert str(Period((date_unit, instant, size))) == expected @pytest.mark.parametrize("date_unit, instant, size, expected", [ - [DateUnit.DAY, Instant((2022, 1, 1)), 1, "2022-01-01"], - [DateUnit.DAY, Instant((2022, 1, 1)), 3, "day:2022-01-01:3"], - [DateUnit.DAY, Instant((2022, 3, 1)), 3, "day:2022-03-01:3"], + [periods.DAY, Instant((2022, 1, 1)), 1, "2022-01-01"], + [periods.DAY, Instant((2022, 1, 1)), 3, "day:2022-01-01:3"], + [periods.DAY, Instant((2022, 3, 1)), 3, "day:2022-03-01:3"], ]) def test_str_with_days(date_unit, instant, size, expected): assert str(Period((date_unit, instant, size))) == expected diff --git a/tests/core/test_periods.py b/tests/core/test_periods.py index 58fffd7919..0816a0ce2e 100644 --- a/tests/core/test_periods.py +++ b/tests/core/test_periods.py @@ -3,137 +3,7 @@ import pytest -from openfisca_core.periods import Period, Instant, YEAR, MONTH, DAY, period - -first_jan = Instant((2014, 1, 1)) -first_march = Instant((2014, 3, 1)) - - -''' -Test String -> Period -''' - -# Years - - -def test_parsing_year(): - assert period('2014') == Period((YEAR, first_jan, 1)) - - -def test_parsing_rolling_year(): - assert period('year:2014-03') == Period((YEAR, first_march, 1)) - - -def test_parsing_several_years(): - assert period('year:2014:2') == Period((YEAR, first_jan, 2)) - - -def test_wrong_syntax_several_years(): - with pytest.raises(ValueError): - period('2014:2') - - -# Months - -def test_parsing_month(): - assert period('2014-01') == Period((MONTH, first_jan, 1)) - - -def test_parsing_several_months(): - assert period('month:2014-03:3') == Period((MONTH, first_march, 3)) - - -def test_wrong_syntax_several_months(): - with pytest.raises(ValueError): - period('2014-3:3') - - -# Days - -def test_parsing_day(): - assert period('2014-01-01') == Period((DAY, first_jan, 1)) - - -def test_parsing_several_days(): - assert period('day:2014-03-01:3') == Period((DAY, first_march, 3)) - - -def test_wrong_syntax_several_days(): - with pytest.raises(ValueError): - period('2014-2-3:2') - - -def test_day_size_in_days(): - assert Period(('day', Instant((2014, 12, 31)), 1)).size_in_days == 1 - - -def test_3_day_size_in_days(): - assert Period(('day', Instant((2014, 12, 31)), 3)).size_in_days == 3 - - -def test_month_size_in_days(): - assert Period(('month', Instant((2014, 12, 1)), 1)).size_in_days == 31 - - -def test_leap_month_size_in_days(): - assert Period(('month', Instant((2012, 2, 3)), 1)).size_in_days == 29 - - -def test_3_month_size_in_days(): - assert Period(('month', Instant((2013, 1, 3)), 3)).size_in_days == 31 + 28 + 31 - - -def test_leap_3_month_size_in_days(): - assert Period(('month', Instant((2012, 1, 3)), 3)).size_in_days == 31 + 29 + 31 - - -def test_year_size_in_days(): - assert Period(('year', Instant((2014, 12, 1)), 1)).size_in_days == 365 - - -def test_leap_year_size_in_days(): - assert Period(('year', Instant((2012, 1, 1)), 1)).size_in_days == 366 - - -def test_2_years_size_in_days(): - assert Period(('year', Instant((2014, 1, 1)), 2)).size_in_days == 730 - -# Misc - - -def test_wrong_date(): - with pytest.raises(ValueError): - period("2006-31-03") - - -def test_ambiguous_period(): - with pytest.raises(ValueError): - period('month:2014') - - -def test_deprecated_signature(): - with pytest.raises(TypeError): - period(MONTH, 2014) - - -def test_wrong_argument(): - with pytest.raises(ValueError): - period({}) - - -def test_wrong_argument_1(): - with pytest.raises(ValueError): - period([]) - - -def test_none(): - with pytest.raises(ValueError): - period(None) - - -def test_empty_string(): - with pytest.raises(ValueError): - period('') +from openfisca_core.periods import YEAR, MONTH, DAY, period @pytest.mark.parametrize("test", [ From c7622ff5aa9a6b2a32a1f953d077033a07302ca5 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Sat, 30 Jul 2022 20:26:35 +0200 Subject: [PATCH 21/70] Consolidate tests --- .../periods/tests/helpers/__init__.py | 0 .../helpers/test__parse_simple_period.py | 19 -- .../periods/tests/helpers/test_instant.py | 67 ------ .../tests/helpers/test_instant_date.py | 32 --- .../tests/helpers/test_key_period_size.py | 16 -- .../periods/tests/helpers/test_period.py | 96 -------- .../periods/tests/period/__init__.py | 0 .../periods/tests/period/test_size_in_days.py | 20 -- .../periods/tests/period/test_str.py | 34 --- openfisca_core/periods/tests/test_helpers.py | 210 ++++++++++++++++++ openfisca_core/periods/tests/test_period.py | 80 +++++++ tests/core/test_periods.py | 26 --- 12 files changed, 290 insertions(+), 310 deletions(-) delete mode 100644 openfisca_core/periods/tests/helpers/__init__.py delete mode 100644 openfisca_core/periods/tests/helpers/test__parse_simple_period.py delete mode 100644 openfisca_core/periods/tests/helpers/test_instant.py delete mode 100644 openfisca_core/periods/tests/helpers/test_instant_date.py delete mode 100644 openfisca_core/periods/tests/helpers/test_key_period_size.py delete mode 100644 openfisca_core/periods/tests/helpers/test_period.py delete mode 100644 openfisca_core/periods/tests/period/__init__.py delete mode 100644 openfisca_core/periods/tests/period/test_size_in_days.py delete mode 100644 openfisca_core/periods/tests/period/test_str.py create mode 100644 openfisca_core/periods/tests/test_helpers.py create mode 100644 openfisca_core/periods/tests/test_period.py delete mode 100644 tests/core/test_periods.py diff --git a/openfisca_core/periods/tests/helpers/__init__.py b/openfisca_core/periods/tests/helpers/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/openfisca_core/periods/tests/helpers/test__parse_simple_period.py b/openfisca_core/periods/tests/helpers/test__parse_simple_period.py deleted file mode 100644 index 081d795e85..0000000000 --- a/openfisca_core/periods/tests/helpers/test__parse_simple_period.py +++ /dev/null @@ -1,19 +0,0 @@ -import pytest - -from openfisca_core import periods -from openfisca_core.periods import Instant, Period, helpers - - -@pytest.mark.parametrize("arg, expected", [ - ["1", None], - ["999", None], - ["1000", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], - ["1000-1", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], - ["1000-01", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], - ["1000-1-1", Period((periods.DAY, Instant((1000, 1, 1)), 1))], - ["1000-01-1", Period((periods.DAY, Instant((1000, 1, 1)), 1))], - ["1000-01-01", Period((periods.DAY, Instant((1000, 1, 1)), 1))], - ["1000-01-99", None], - ]) -def test__parse_simple_period_with_a_valid_argument(arg, expected): - assert helpers._parse_simple_period(arg) == expected diff --git a/openfisca_core/periods/tests/helpers/test_instant.py b/openfisca_core/periods/tests/helpers/test_instant.py deleted file mode 100644 index c58c5897f2..0000000000 --- a/openfisca_core/periods/tests/helpers/test_instant.py +++ /dev/null @@ -1,67 +0,0 @@ -import datetime - -import pytest - -from openfisca_core import periods -from openfisca_core.periods import Instant, Period - - -@pytest.mark.parametrize("arg, expected", [ - [None, None], - [datetime.date(1, 1, 1), Instant((1, 1, 1))], - [Instant((1, 1, 1)), Instant((1, 1, 1))], - [Period((periods.DAY, Instant((1, 1, 1)), 365)), Instant((1, 1, 1))], - [-1, Instant((-1, 1, 1))], - [0, Instant((0, 1, 1))], - [1, Instant((1, 1, 1))], - [999, Instant((999, 1, 1))], - [1000, Instant((1000, 1, 1))], - ["1000", Instant((1000, 1, 1))], - ["1000-01", Instant((1000, 1, 1))], - ["1000-01-01", Instant((1000, 1, 1))], - [(None,), Instant((None, 1, 1))], - [(None, None), Instant((None, None, 1))], - [(None, None, None), Instant((None, None, None))], - [(datetime.date(1, 1, 1),), Instant((datetime.date(1, 1, 1), 1, 1))], - [(Instant((1, 1, 1)),), Instant((Instant((1, 1, 1)), 1, 1))], - [(Period((periods.DAY, Instant((1, 1, 1)), 365)),), Instant((Period((periods.DAY, Instant((1, 1, 1)), 365)), 1, 1))], - [(-1,), Instant((-1, 1, 1))], - [(-1, -1), Instant((-1, -1, 1))], - [(-1, -1, -1), Instant((-1, -1, -1))], - [("-1",), Instant(("-1", 1, 1))], - [("-1", "-1"), Instant(("-1", "-1", 1))], - [("-1", "-1", "-1"), Instant(("-1", "-1", "-1"))], - [("1-1",), Instant(("1-1", 1, 1))], - [("1-1-1",), Instant(("1-1-1", 1, 1))], - ]) -def test_instant_with_a_valid_argument(arg, expected): - assert periods.instant(arg) == expected - - -@pytest.mark.parametrize("arg, error", [ - [periods.YEAR, ValueError], - [periods.ETERNITY, ValueError], - ["1000-0", ValueError], - ["1000-0-0", ValueError], - ["1000-1", ValueError], - ["1000-1-1", ValueError], - ["1", ValueError], - ["a", ValueError], - ["year", ValueError], - ["eternity", ValueError], - ["999", ValueError], - ["1:1000-01-01", ValueError], - ["a:1000-01-01", ValueError], - ["year:1000-01-01", ValueError], - ["year:1000-01-01:1", ValueError], - ["year:1000-01-01:3", ValueError], - ["1000-01-01:a", ValueError], - ["1000-01-01:1", ValueError], - [(), AssertionError], - [{}, AssertionError], - ["", ValueError], - [(None, None, None, None), AssertionError], - ]) -def test_instant_with_an_invalid_argument(arg, error): - with pytest.raises(error): - periods.instant(arg) diff --git a/openfisca_core/periods/tests/helpers/test_instant_date.py b/openfisca_core/periods/tests/helpers/test_instant_date.py deleted file mode 100644 index 722728a6e4..0000000000 --- a/openfisca_core/periods/tests/helpers/test_instant_date.py +++ /dev/null @@ -1,32 +0,0 @@ -import datetime - -import pytest - -from openfisca_core import periods -from openfisca_core.periods import Instant - - -@pytest.mark.parametrize("arg, expected", [ - [None, None], - [Instant((1, 1, 1)), datetime.date(1, 1, 1)], - [Instant((4, 2, 29)), datetime.date(4, 2, 29)], - [(1, 1, 1), datetime.date(1, 1, 1)], - ]) -def test_instant_date_with_a_valid_argument(arg, expected): - assert periods.instant_date(arg) == expected - - -@pytest.mark.parametrize("arg, error", [ - [Instant((-1, 1, 1)), ValueError], - [Instant((1, -1, 1)), ValueError], - [Instant((1, 13, -1)), ValueError], - [Instant((1, 1, -1)), ValueError], - [Instant((1, 1, 32)), ValueError], - [Instant((1, 2, 29)), ValueError], - [Instant(("1", 1, 1)), TypeError], - [(1,), TypeError], - [(1, 1), TypeError], - ]) -def test_instant_date_with_an_invalid_argument(arg, error): - with pytest.raises(error): - periods.instant_date(arg) diff --git a/openfisca_core/periods/tests/helpers/test_key_period_size.py b/openfisca_core/periods/tests/helpers/test_key_period_size.py deleted file mode 100644 index 1094d4e42e..0000000000 --- a/openfisca_core/periods/tests/helpers/test_key_period_size.py +++ /dev/null @@ -1,16 +0,0 @@ -import pytest - -from openfisca_core import periods -from openfisca_core.periods import Instant, Period - - -@pytest.mark.parametrize("arg, expected", [ - [Period((periods.DAY, Instant((1, 1, 1)), 365)), "100_365"], - [Period((periods.MONTH, Instant((1, 1, 1)), 12)), "200_12"], - [Period((periods.YEAR, Instant((1, 1, 1)), 2)), "300_2"], - [Period((periods.ETERNITY, Instant((1, 1, 1)), 1)), "400_1"], - [(periods.DAY, None, 1), "100_1"], - [(periods.MONTH, None, -1000), "200_-1000"], - ]) -def test_key_period_size_with_a_valid_argument(arg, expected): - assert periods.key_period_size(arg) == expected diff --git a/openfisca_core/periods/tests/helpers/test_period.py b/openfisca_core/periods/tests/helpers/test_period.py deleted file mode 100644 index 50cc59eae8..0000000000 --- a/openfisca_core/periods/tests/helpers/test_period.py +++ /dev/null @@ -1,96 +0,0 @@ -import datetime - -import pytest - -from openfisca_core import periods -from openfisca_core.periods import Instant, Period - - -@pytest.mark.parametrize("arg, expected", [ - ["eternity", Period((periods.ETERNITY, Instant((1, 1, 1)), float("inf")))], - ["ETERNITY", Period((periods.ETERNITY, Instant((1, 1, 1)), float("inf")))], - [periods.ETERNITY, Period((periods.ETERNITY, Instant((1, 1, 1)), float("inf")))], - [Instant((1, 1, 1)), Period((periods.DAY, Instant((1, 1, 1)), 1))], - [Period((periods.DAY, Instant((1, 1, 1)), 365)), Period((periods.DAY, Instant((1, 1, 1)), 365))], - [-1, Period((periods.YEAR, Instant((-1, 1, 1)), 1))], - [0, Period((periods.YEAR, Instant((0, 1, 1)), 1))], - [1, Period((periods.YEAR, Instant((1, 1, 1)), 1))], - [999, Period((periods.YEAR, Instant((999, 1, 1)), 1))], - [1000, Period((periods.YEAR, Instant((1000, 1, 1)), 1))], - ["1000", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], - ["1000-1", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], - ["1000-1-1", Period((periods.DAY, Instant((1000, 1, 1)), 1))], - ["1000-01", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], - ["1000-01-01", Period((periods.DAY, Instant((1000, 1, 1)), 1))], - ["1004-02-29", Period((periods.DAY, Instant((1004, 2, 29)), 1))], - ["year:1000", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], - ["year:1000-01", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], - ["year:1000-01-01", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], - ["year:1000:1", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], - ["year:1000-01:1", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], - ["year:1000-01-01:1", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], - ["year:1000:3", Period((periods.YEAR, Instant((1000, 1, 1)), 3))], - ["year:1000-01:3", Period((periods.YEAR, Instant((1000, 1, 1)), 3))], - ["month:1000-01-01:3", Period((periods.MONTH, Instant((1000, 1, 1)), 3))], - ["month:1000-01", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], - ["month:1000-01-01", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], - ["month:1000-01:1", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], - ["month:1000-01:3", Period((periods.MONTH, Instant((1000, 1, 1)), 3))], - ["month:1000-01-01:3", Period((periods.MONTH, Instant((1000, 1, 1)), 3))], - ["month:1000-01-01:3", Period((periods.MONTH, Instant((1000, 1, 1)), 3))], - ["day:1000-01-01", Period((periods.DAY, Instant((1000, 1, 1)), 1))], - ["day:1000-01-01:3", Period((periods.DAY, Instant((1000, 1, 1)), 3))], - ]) -def test_instant_with_a_valid_argument(arg, expected): - assert periods.period(arg) == expected - - -@pytest.mark.parametrize("arg, error", [ - [None, ValueError], - [periods.YEAR, ValueError], - [datetime.date(1, 1, 1), ValueError], - ["1000-0", ValueError], - ["1000-13", ValueError], - ["1000-0-0", ValueError], - ["1000-1-0", ValueError], - ["1000-2-31", ValueError], - ["1", ValueError], - ["a", ValueError], - ["year", ValueError], - ["999", ValueError], - ["1:1000", ValueError], - ["a:1000", ValueError], - ["month:1000", ValueError], - ["day:1000-01", ValueError], - ["1000:a", ValueError], - ["1000:1", ValueError], - ["1000-01:1", ValueError], - ["1000-01-01:1", ValueError], - ["month:1000:1", ValueError], - ["day:1000:1", ValueError], - ["day:1000-01:1", ValueError], - [(), ValueError], - [{}, ValueError], - ["", ValueError], - [(None,), ValueError], - [(None, None), ValueError], - [(None, None, None), ValueError], - [(None, None, None, None), ValueError], - [(datetime.date(1, 1, 1),), ValueError], - [(Instant((1, 1, 1)),), ValueError], - [(Period((periods.DAY, Instant((1, 1, 1)), 365)),), ValueError], - [(1,), ValueError], - [(1, 1), ValueError], - [(1, 1, 1), ValueError], - [(-1,), ValueError], - [(-1, -1), ValueError], - [(-1, -1, -1), ValueError], - [("-1",), ValueError], - [("-1", "-1"), ValueError], - [("-1", "-1", "-1"), ValueError], - [("1-1",), ValueError], - [("1-1-1",), ValueError], - ]) -def test_instant_with_an_invalid_argument(arg, error): - with pytest.raises(error): - periods.period(arg) diff --git a/openfisca_core/periods/tests/period/__init__.py b/openfisca_core/periods/tests/period/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/openfisca_core/periods/tests/period/test_size_in_days.py b/openfisca_core/periods/tests/period/test_size_in_days.py deleted file mode 100644 index c68d5d82b0..0000000000 --- a/openfisca_core/periods/tests/period/test_size_in_days.py +++ /dev/null @@ -1,20 +0,0 @@ -import pytest - -from openfisca_core import periods -from openfisca_core.periods import Instant, Period - - -@pytest.mark.parametrize("date_unit, instant, size, expected", [ - [periods.DAY, Instant((2022, 12, 31)), 1, 1], - [periods.DAY, Instant((2022, 12, 31)), 3, 3], - [periods.MONTH, Instant((2022, 12, 1)), 1, 31], - [periods.MONTH, Instant((2012, 2, 3)), 1, 29], - [periods.MONTH, Instant((2022, 1, 3)), 3, 31 + 28 + 31], - [periods.MONTH, Instant((2012, 1, 3)), 3, 31 + 29 + 31], - [periods.YEAR, Instant((2022, 12, 1)), 1, 365], - [periods.YEAR, Instant((2012, 1, 1)), 1, 366], - [periods.YEAR, Instant((2022, 1, 1)), 2, 730], - ]) -def test_day_size_in_days(date_unit, instant, size, expected): - period = Period((date_unit, instant, size)) - assert period.size_in_days == expected diff --git a/openfisca_core/periods/tests/period/test_str.py b/openfisca_core/periods/tests/period/test_str.py deleted file mode 100644 index 4e0ba81446..0000000000 --- a/openfisca_core/periods/tests/period/test_str.py +++ /dev/null @@ -1,34 +0,0 @@ -import pytest - -from openfisca_core import periods -from openfisca_core.periods import Instant, Period - - -@pytest.mark.parametrize("date_unit, instant, size, expected", [ - [periods.YEAR, Instant((2022, 1, 1)), 1, "2022"], - [periods.MONTH, Instant((2022, 1, 1)), 12, "2022"], - [periods.YEAR, Instant((2022, 3, 1)), 1, "year:2022-03"], - [periods.MONTH, Instant((2022, 3, 1)), 12, "year:2022-03"], - [periods.YEAR, Instant((2022, 1, 1)), 3, "year:2022:3"], - [periods.YEAR, Instant((2022, 1, 3)), 3, "year:2022:3"], - ]) -def test_str_with_years(date_unit, instant, size, expected): - assert str(Period((date_unit, instant, size))) == expected - - -@pytest.mark.parametrize("date_unit, instant, size, expected", [ - [periods.MONTH, Instant((2022, 1, 1)), 1, "2022-01"], - [periods.MONTH, Instant((2022, 1, 1)), 3, "month:2022-01:3"], - [periods.MONTH, Instant((2022, 3, 1)), 3, "month:2022-03:3"], - ]) -def test_str_with_months(date_unit, instant, size, expected): - assert str(Period((date_unit, instant, size))) == expected - - -@pytest.mark.parametrize("date_unit, instant, size, expected", [ - [periods.DAY, Instant((2022, 1, 1)), 1, "2022-01-01"], - [periods.DAY, Instant((2022, 1, 1)), 3, "day:2022-01-01:3"], - [periods.DAY, Instant((2022, 3, 1)), 3, "day:2022-03-01:3"], - ]) -def test_str_with_days(date_unit, instant, size, expected): - assert str(Period((date_unit, instant, size))) == expected diff --git a/openfisca_core/periods/tests/test_helpers.py b/openfisca_core/periods/tests/test_helpers.py new file mode 100644 index 0000000000..6dd09788b9 --- /dev/null +++ b/openfisca_core/periods/tests/test_helpers.py @@ -0,0 +1,210 @@ +import datetime + +import pytest + +from openfisca_core import periods +from openfisca_core.periods import Instant, Period, helpers + + +@pytest.mark.parametrize("arg, expected", [ + [None, None], + [datetime.date(1, 1, 1), Instant((1, 1, 1))], + [Instant((1, 1, 1)), Instant((1, 1, 1))], + [Period((periods.DAY, Instant((1, 1, 1)), 365)), Instant((1, 1, 1))], + [-1, Instant((-1, 1, 1))], + [0, Instant((0, 1, 1))], + [1, Instant((1, 1, 1))], + [999, Instant((999, 1, 1))], + [1000, Instant((1000, 1, 1))], + ["1000", Instant((1000, 1, 1))], + ["1000-01", Instant((1000, 1, 1))], + ["1000-01-01", Instant((1000, 1, 1))], + [(None,), Instant((None, 1, 1))], + [(None, None), Instant((None, None, 1))], + [(None, None, None), Instant((None, None, None))], + [(datetime.date(1, 1, 1),), Instant((datetime.date(1, 1, 1), 1, 1))], + [(Instant((1, 1, 1)),), Instant((Instant((1, 1, 1)), 1, 1))], + [(Period((periods.DAY, Instant((1, 1, 1)), 365)),), Instant((Period((periods.DAY, Instant((1, 1, 1)), 365)), 1, 1))], + [(-1,), Instant((-1, 1, 1))], + [(-1, -1), Instant((-1, -1, 1))], + [(-1, -1, -1), Instant((-1, -1, -1))], + [("-1",), Instant(("-1", 1, 1))], + [("-1", "-1"), Instant(("-1", "-1", 1))], + [("-1", "-1", "-1"), Instant(("-1", "-1", "-1"))], + [("1-1",), Instant(("1-1", 1, 1))], + [("1-1-1",), Instant(("1-1-1", 1, 1))], + ]) +def test_instant_with_a_valid_argument(arg, expected): + assert periods.instant(arg) == expected + + +@pytest.mark.parametrize("arg, error", [ + [periods.YEAR, ValueError], + [periods.ETERNITY, ValueError], + ["1000-0", ValueError], + ["1000-0-0", ValueError], + ["1000-1", ValueError], + ["1000-1-1", ValueError], + ["1", ValueError], + ["a", ValueError], + ["year", ValueError], + ["eternity", ValueError], + ["999", ValueError], + ["1:1000-01-01", ValueError], + ["a:1000-01-01", ValueError], + ["year:1000-01-01", ValueError], + ["year:1000-01-01:1", ValueError], + ["year:1000-01-01:3", ValueError], + ["1000-01-01:a", ValueError], + ["1000-01-01:1", ValueError], + [(), AssertionError], + [{}, AssertionError], + ["", ValueError], + [(None, None, None, None), AssertionError], + ]) +def test_instant_with_an_invalid_argument(arg, error): + with pytest.raises(error): + periods.instant(arg) + + +@pytest.mark.parametrize("arg, expected", [ + [None, None], + [Instant((1, 1, 1)), datetime.date(1, 1, 1)], + [Instant((4, 2, 29)), datetime.date(4, 2, 29)], + [(1, 1, 1), datetime.date(1, 1, 1)], + ]) +def test_instant_date_with_a_valid_argument(arg, expected): + assert periods.instant_date(arg) == expected + + +@pytest.mark.parametrize("arg, error", [ + [Instant((-1, 1, 1)), ValueError], + [Instant((1, -1, 1)), ValueError], + [Instant((1, 13, -1)), ValueError], + [Instant((1, 1, -1)), ValueError], + [Instant((1, 1, 32)), ValueError], + [Instant((1, 2, 29)), ValueError], + [Instant(("1", 1, 1)), TypeError], + [(1,), TypeError], + [(1, 1), TypeError], + ]) +def test_instant_date_with_an_invalid_argument(arg, error): + with pytest.raises(error): + periods.instant_date(arg) + + +@pytest.mark.parametrize("arg, expected", [ + ["eternity", Period((periods.ETERNITY, Instant((1, 1, 1)), float("inf")))], + ["ETERNITY", Period((periods.ETERNITY, Instant((1, 1, 1)), float("inf")))], + [periods.ETERNITY, Period((periods.ETERNITY, Instant((1, 1, 1)), float("inf")))], + [Instant((1, 1, 1)), Period((periods.DAY, Instant((1, 1, 1)), 1))], + [Period((periods.DAY, Instant((1, 1, 1)), 365)), Period((periods.DAY, Instant((1, 1, 1)), 365))], + [-1, Period((periods.YEAR, Instant((-1, 1, 1)), 1))], + [0, Period((periods.YEAR, Instant((0, 1, 1)), 1))], + [1, Period((periods.YEAR, Instant((1, 1, 1)), 1))], + [999, Period((periods.YEAR, Instant((999, 1, 1)), 1))], + [1000, Period((periods.YEAR, Instant((1000, 1, 1)), 1))], + ["1000", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], + ["1000-1", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], + ["1000-1-1", Period((periods.DAY, Instant((1000, 1, 1)), 1))], + ["1000-01", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], + ["1000-01-01", Period((periods.DAY, Instant((1000, 1, 1)), 1))], + ["1004-02-29", Period((periods.DAY, Instant((1004, 2, 29)), 1))], + ["year:1000", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], + ["year:1000-01", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], + ["year:1000-01-01", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], + ["year:1000:1", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], + ["year:1000-01:1", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], + ["year:1000-01-01:1", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], + ["year:1000:3", Period((periods.YEAR, Instant((1000, 1, 1)), 3))], + ["year:1000-01:3", Period((periods.YEAR, Instant((1000, 1, 1)), 3))], + ["month:1000-01-01:3", Period((periods.MONTH, Instant((1000, 1, 1)), 3))], + ["month:1000-01", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], + ["month:1000-01-01", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], + ["month:1000-01:1", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], + ["month:1000-01:3", Period((periods.MONTH, Instant((1000, 1, 1)), 3))], + ["month:1000-01-01:3", Period((periods.MONTH, Instant((1000, 1, 1)), 3))], + ["month:1000-01-01:3", Period((periods.MONTH, Instant((1000, 1, 1)), 3))], + ["day:1000-01-01", Period((periods.DAY, Instant((1000, 1, 1)), 1))], + ["day:1000-01-01:3", Period((periods.DAY, Instant((1000, 1, 1)), 3))], + ]) +def test_period_with_a_valid_argument(arg, expected): + assert periods.period(arg) == expected + + +@pytest.mark.parametrize("arg, error", [ + [None, ValueError], + [periods.YEAR, ValueError], + [datetime.date(1, 1, 1), ValueError], + ["1000-0", ValueError], + ["1000-13", ValueError], + ["1000-0-0", ValueError], + ["1000-1-0", ValueError], + ["1000-2-31", ValueError], + ["1", ValueError], + ["a", ValueError], + ["year", ValueError], + ["999", ValueError], + ["1:1000", ValueError], + ["a:1000", ValueError], + ["month:1000", ValueError], + ["day:1000-01", ValueError], + ["1000:a", ValueError], + ["1000:1", ValueError], + ["1000-01:1", ValueError], + ["1000-01-01:1", ValueError], + ["month:1000:1", ValueError], + ["day:1000:1", ValueError], + ["day:1000-01:1", ValueError], + [(), ValueError], + [{}, ValueError], + ["", ValueError], + [(None,), ValueError], + [(None, None), ValueError], + [(None, None, None), ValueError], + [(None, None, None, None), ValueError], + [(datetime.date(1, 1, 1),), ValueError], + [(Instant((1, 1, 1)),), ValueError], + [(Period((periods.DAY, Instant((1, 1, 1)), 365)),), ValueError], + [(1,), ValueError], + [(1, 1), ValueError], + [(1, 1, 1), ValueError], + [(-1,), ValueError], + [(-1, -1), ValueError], + [(-1, -1, -1), ValueError], + [("-1",), ValueError], + [("-1", "-1"), ValueError], + [("-1", "-1", "-1"), ValueError], + [("1-1",), ValueError], + [("1-1-1",), ValueError], + ]) +def test_period_with_an_invalid_argument(arg, error): + with pytest.raises(error): + periods.period(arg) + + +@pytest.mark.parametrize("arg, expected", [ + ["1", None], + ["999", None], + ["1000", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], + ["1000-1", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], + ["1000-01", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], + ["1000-1-1", Period((periods.DAY, Instant((1000, 1, 1)), 1))], + ["1000-01-1", Period((periods.DAY, Instant((1000, 1, 1)), 1))], + ["1000-01-01", Period((periods.DAY, Instant((1000, 1, 1)), 1))], + ["1000-01-99", None], + ]) +def test__parse_simple_period_with_a_valid_argument(arg, expected): + assert helpers._parse_simple_period(arg) == expected + + +@pytest.mark.parametrize("arg, expected", [ + [Period((periods.DAY, Instant((1, 1, 1)), 365)), "100_365"], + [Period((periods.MONTH, Instant((1, 1, 1)), 12)), "200_12"], + [Period((periods.YEAR, Instant((1, 1, 1)), 2)), "300_2"], + [Period((periods.ETERNITY, Instant((1, 1, 1)), 1)), "400_1"], + [(periods.DAY, None, 1), "100_1"], + [(periods.MONTH, None, -1000), "200_-1000"], + ]) +def test_key_period_size_with_a_valid_argument(arg, expected): + assert periods.key_period_size(arg) == expected diff --git a/openfisca_core/periods/tests/test_period.py b/openfisca_core/periods/tests/test_period.py new file mode 100644 index 0000000000..4aab4d3339 --- /dev/null +++ b/openfisca_core/periods/tests/test_period.py @@ -0,0 +1,80 @@ +import pytest + +from openfisca_core import periods +from openfisca_core.periods import Instant, Period + + +@pytest.mark.parametrize("date_unit, instant, size, expected", [ + [periods.YEAR, Instant((2022, 1, 1)), 1, "2022"], + [periods.MONTH, Instant((2022, 1, 1)), 12, "2022"], + [periods.YEAR, Instant((2022, 3, 1)), 1, "year:2022-03"], + [periods.MONTH, Instant((2022, 3, 1)), 12, "year:2022-03"], + [periods.YEAR, Instant((2022, 1, 1)), 3, "year:2022:3"], + [periods.YEAR, Instant((2022, 1, 3)), 3, "year:2022:3"], + ]) +def test_str_with_years(date_unit, instant, size, expected): + assert str(Period((date_unit, instant, size))) == expected + + +@pytest.mark.parametrize("date_unit, instant, size, expected", [ + [periods.MONTH, Instant((2022, 1, 1)), 1, "2022-01"], + [periods.MONTH, Instant((2022, 1, 1)), 3, "month:2022-01:3"], + [periods.MONTH, Instant((2022, 3, 1)), 3, "month:2022-03:3"], + ]) +def test_str_with_months(date_unit, instant, size, expected): + assert str(Period((date_unit, instant, size))) == expected + + +@pytest.mark.parametrize("date_unit, instant, size, expected", [ + [periods.DAY, Instant((2022, 1, 1)), 1, "2022-01-01"], + [periods.DAY, Instant((2022, 1, 1)), 3, "day:2022-01-01:3"], + [periods.DAY, Instant((2022, 3, 1)), 3, "day:2022-03-01:3"], + ]) +def test_str_with_days(date_unit, instant, size, expected): + assert str(Period((date_unit, instant, size))) == expected + + +@pytest.mark.parametrize("period, unit, length, first, last", [ + (periods.period('year:2014:2'), periods.YEAR, 2, periods.period('2014'), periods.period('2015')), + (periods.period(2017), periods.MONTH, 12, periods.period('2017-01'), periods.period('2017-12')), + (periods.period('year:2014:2'), periods.MONTH, 24, periods.period('2014-01'), periods.period('2015-12')), + (periods.period('month:2014-03:3'), periods.MONTH, 3, periods.period('2014-03'), periods.period('2014-05')), + (periods.period(2017), periods.DAY, 365, periods.period('2017-01-01'), periods.period('2017-12-31')), + (periods.period('year:2014:2'), periods.DAY, 730, periods.period('2014-01-01'), periods.period('2015-12-31')), + (periods.period('month:2014-03:3'), periods.DAY, 92, periods.period('2014-03-01'), periods.period('2014-05-31')), + ]) +def test_subperiods(period, unit, length, first, last): + subperiods = period.get_subperiods(unit) + assert len(subperiods) == length + assert subperiods[0] == first + assert subperiods[-1] == last + + +@pytest.mark.parametrize("date_unit, instant, size, expected", [ + [periods.MONTH, Instant((2022, 12, 1)), 1, 1], + [periods.MONTH, Instant((2012, 2, 3)), 1, 1], + [periods.MONTH, Instant((2022, 1, 3)), 3, 3], + [periods.MONTH, Instant((2012, 1, 3)), 3, 3], + [periods.YEAR, Instant((2022, 12, 1)), 1, 12], + [periods.YEAR, Instant((2012, 1, 1)), 1, 12], + [periods.YEAR, Instant((2022, 1, 1)), 2, 24], + ]) +def test_day_size_in_months(date_unit, instant, size, expected): + period = Period((date_unit, instant, size)) + assert period.size_in_months == expected + + +@pytest.mark.parametrize("date_unit, instant, size, expected", [ + [periods.DAY, Instant((2022, 12, 31)), 1, 1], + [periods.DAY, Instant((2022, 12, 31)), 3, 3], + [periods.MONTH, Instant((2022, 12, 1)), 1, 31], + [periods.MONTH, Instant((2012, 2, 3)), 1, 29], + [periods.MONTH, Instant((2022, 1, 3)), 3, 31 + 28 + 31], + [periods.MONTH, Instant((2012, 1, 3)), 3, 31 + 29 + 31], + [periods.YEAR, Instant((2022, 12, 1)), 1, 365], + [periods.YEAR, Instant((2012, 1, 1)), 1, 366], + [periods.YEAR, Instant((2022, 1, 1)), 2, 730], + ]) +def test_day_size_in_days(date_unit, instant, size, expected): + period = Period((date_unit, instant, size)) + assert period.size_in_days == expected diff --git a/tests/core/test_periods.py b/tests/core/test_periods.py deleted file mode 100644 index 0816a0ce2e..0000000000 --- a/tests/core/test_periods.py +++ /dev/null @@ -1,26 +0,0 @@ -# -*- coding: utf-8 -*- - - -import pytest - -from openfisca_core.periods import YEAR, MONTH, DAY, period - - -@pytest.mark.parametrize("test", [ - (period('year:2014:2'), YEAR, 2, period('2014'), period('2015')), - (period(2017), MONTH, 12, period('2017-01'), period('2017-12')), - (period('year:2014:2'), MONTH, 24, period('2014-01'), period('2015-12')), - (period('month:2014-03:3'), MONTH, 3, period('2014-03'), period('2014-05')), - (period(2017), DAY, 365, period('2017-01-01'), period('2017-12-31')), - (period('year:2014:2'), DAY, 730, period('2014-01-01'), period('2015-12-31')), - (period('month:2014-03:3'), DAY, 92, period('2014-03-01'), period('2014-05-31')), - ]) -def test_subperiods(test): - - def check_subperiods(period, unit, length, first, last): - subperiods = period.get_subperiods(unit) - assert len(subperiods) == length - assert subperiods[0] == first - assert subperiods[-1] == last - - check_subperiods(*test) From af0f132f34516ab7eaf142211ee11eea9ad4fe8e Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Thu, 28 Jul 2022 21:52:03 +0200 Subject: [PATCH 22/70] Version & CHANGELOG bump --- CHANGELOG.md | 6 ++++++ setup.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b796fd3c0..e995522107 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +### 35.8.6 [#1138](https://github.com/openfisca/openfisca-core/pull/1138) + +#### Technical changes + +- Fix `openfisca_core.periods` doctests. + ### 35.8.5 [#1137](https://github.com/openfisca/openfisca-core/pull/1137) #### Technical changes diff --git a/setup.py b/setup.py index ceb3bcfda0..c377fc5034 100644 --- a/setup.py +++ b/setup.py @@ -47,7 +47,7 @@ setup( name = 'OpenFisca-Core', - version = '35.8.5', + version = '35.8.6', author = 'OpenFisca Team', author_email = 'contact@openfisca.org', classifiers = [ From ad22d9eeffecce35971a1e60023989bcbab2fa23 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Fri, 29 Jul 2022 04:12:58 +0200 Subject: [PATCH 23/70] Add date units to periods --- .../data_storage/in_memory_storage.py | 7 +- .../data_storage/on_disk_storage.py | 7 +- openfisca_core/holders/helpers.py | 18 +- openfisca_core/holders/holder.py | 13 +- openfisca_core/periods/__init__.py | 1 + openfisca_core/periods/config.py | 16 +- openfisca_core/periods/date_unit.py | 54 +++ openfisca_core/periods/helpers.py | 71 ++-- openfisca_core/periods/instant_.py | 37 +- openfisca_core/periods/period_.py | 355 +++++++++--------- openfisca_core/periods/tests/test_helpers.py | 104 ++--- openfisca_core/periods/tests/test_period.py | 72 ++-- .../scripts/measure_performances.py | 18 +- .../measure_performances_fancy_indexing.py | 5 +- openfisca_core/simulations/simulation.py | 18 +- openfisca_core/tools/simulation_dumper.py | 4 +- openfisca_core/variables/variable.py | 6 +- setup.py | 1 + .../test_fancy_indexing.py | 5 +- tests/core/test_calculate_output.py | 9 +- tests/core/test_countries.py | 5 +- tests/core/test_cycles.py | 17 +- tests/core/test_formulas.py | 17 +- tests/core/test_holders.py | 5 +- tests/core/test_opt_out_cache.py | 8 +- tests/core/test_projectors.py | 23 +- tests/core/test_reforms.py | 27 +- tests/core/test_simulation_builder.py | 9 +- .../tools/test_runner/test_yaml_runner.py | 4 +- tests/core/variables/test_annualize.py | 15 +- tests/core/variables/test_variables.py | 41 +- tests/fixtures/appclient.py | 2 +- tests/fixtures/variables.py | 4 +- 33 files changed, 541 insertions(+), 457 deletions(-) create mode 100644 openfisca_core/periods/date_unit.py diff --git a/openfisca_core/data_storage/in_memory_storage.py b/openfisca_core/data_storage/in_memory_storage.py index bd40460a56..a82977c9d0 100644 --- a/openfisca_core/data_storage/in_memory_storage.py +++ b/openfisca_core/data_storage/in_memory_storage.py @@ -1,6 +1,7 @@ import numpy from openfisca_core import periods +from openfisca_core.periods import DateUnit class InMemoryStorage: @@ -14,7 +15,7 @@ def __init__(self, is_eternal = False): def get(self, period): if self.is_eternal: - period = periods.period(periods.ETERNITY) + period = periods.period(DateUnit.ETERNITY) period = periods.period(period) values = self._arrays.get(period) @@ -24,7 +25,7 @@ def get(self, period): def put(self, value, period): if self.is_eternal: - period = periods.period(periods.ETERNITY) + period = periods.period(DateUnit.ETERNITY) period = periods.period(period) self._arrays[period] = value @@ -35,7 +36,7 @@ def delete(self, period = None): return if self.is_eternal: - period = periods.period(periods.ETERNITY) + period = periods.period(DateUnit.ETERNITY) period = periods.period(period) self._arrays = { diff --git a/openfisca_core/data_storage/on_disk_storage.py b/openfisca_core/data_storage/on_disk_storage.py index 10d4696b58..97bb582785 100644 --- a/openfisca_core/data_storage/on_disk_storage.py +++ b/openfisca_core/data_storage/on_disk_storage.py @@ -4,6 +4,7 @@ import numpy from openfisca_core import periods +from openfisca_core.periods import DateUnit from openfisca_core.indexed_enums import EnumArray @@ -28,7 +29,7 @@ def _decode_file(self, file): def get(self, period): if self.is_eternal: - period = periods.period(periods.ETERNITY) + period = periods.period(DateUnit.ETERNITY) period = periods.period(period) values = self._files.get(period) @@ -38,7 +39,7 @@ def get(self, period): def put(self, value, period): if self.is_eternal: - period = periods.period(periods.ETERNITY) + period = periods.period(DateUnit.ETERNITY) period = periods.period(period) filename = str(period) @@ -55,7 +56,7 @@ def delete(self, period = None): return if self.is_eternal: - period = periods.period(periods.ETERNITY) + period = periods.period(DateUnit.ETERNITY) period = periods.period(period) if period is not None: diff --git a/openfisca_core/holders/helpers.py b/openfisca_core/holders/helpers.py index efe16388e0..96c6d964fa 100644 --- a/openfisca_core/holders/helpers.py +++ b/openfisca_core/holders/helpers.py @@ -2,7 +2,7 @@ import numpy -from openfisca_core import periods +from openfisca_core.periods import DateUnit log = logging.getLogger(__name__) @@ -20,10 +20,10 @@ def set_input_dispatch_by_period(holder, period, array): period_size = period.size period_unit = period.unit - if holder.variable.definition_period == periods.MONTH: - cached_period_unit = periods.MONTH - elif holder.variable.definition_period == periods.YEAR: - cached_period_unit = periods.YEAR + if holder.variable.definition_period == DateUnit.MONTH: + cached_period_unit = DateUnit.MONTH + elif holder.variable.definition_period == DateUnit.YEAR: + cached_period_unit = DateUnit.YEAR else: raise ValueError('set_input_dispatch_by_period can be used only for yearly or monthly variables.') @@ -55,10 +55,10 @@ def set_input_divide_by_period(holder, period, array): period_size = period.size period_unit = period.unit - if holder.variable.definition_period == periods.MONTH: - cached_period_unit = periods.MONTH - elif holder.variable.definition_period == periods.YEAR: - cached_period_unit = periods.YEAR + if holder.variable.definition_period == DateUnit.MONTH: + cached_period_unit = DateUnit.MONTH + elif holder.variable.definition_period == DateUnit.YEAR: + cached_period_unit = DateUnit.YEAR else: raise ValueError('set_input_divide_by_period can be used only for yearly or monthly variables.') diff --git a/openfisca_core/holders/holder.py b/openfisca_core/holders/holder.py index 3d0379d22d..355d50f9d5 100644 --- a/openfisca_core/holders/holder.py +++ b/openfisca_core/holders/holder.py @@ -8,6 +8,7 @@ from openfisca_core.errors import PeriodMismatchError from openfisca_core.data_storage import InMemoryStorage, OnDiskStorage from openfisca_core.indexed_enums import Enum +from openfisca_core.periods import DateUnit class Holder: @@ -19,7 +20,7 @@ def __init__(self, variable, population): self.population = population self.variable = variable self.simulation = population.simulation - self._memory_storage = InMemoryStorage(is_eternal = (self.variable.definition_period == periods.ETERNITY)) + self._memory_storage = InMemoryStorage(is_eternal = (self.variable.definition_period == DateUnit.ETERNITY)) # By default, do not activate on-disk storage, or variable dropping self._disk_storage = None @@ -56,7 +57,7 @@ def create_disk_storage(self, directory = None, preserve = False): os.mkdir(storage_dir) return OnDiskStorage( storage_dir, - is_eternal = (self.variable.definition_period == periods.ETERNITY), + is_eternal = (self.variable.definition_period == DateUnit.ETERNITY), preserve_storage_dir = preserve ) @@ -148,9 +149,9 @@ def set_input(self, period, array): """ period = periods.period(period) - if period.unit == periods.ETERNITY and self.variable.definition_period != periods.ETERNITY: + if period.unit == DateUnit.ETERNITY and self.variable.definition_period != DateUnit.ETERNITY: error_message = os.linesep.join([ - 'Unable to set a value for variable {0} for periods.ETERNITY.', + 'Unable to set a value for variable {0} for DateUnit.ETERNITY.', '{0} is only defined for {1}s. Please adapt your input.', ]).format( self.variable.name, @@ -197,9 +198,9 @@ def _to_array(self, value): def _set(self, period, value): value = self._to_array(value) - if self.variable.definition_period != periods.ETERNITY: + if self.variable.definition_period != DateUnit.ETERNITY: if period is None: - raise ValueError('A period must be specified to set values, except for variables with periods.ETERNITY as as period_definition.') + raise ValueError('A period must be specified to set values, except for variables with DateUnit.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' diff --git a/openfisca_core/periods/__init__.py b/openfisca_core/periods/__init__.py index 4cd9db648c..dd35548818 100644 --- a/openfisca_core/periods/__init__.py +++ b/openfisca_core/periods/__init__.py @@ -42,5 +42,6 @@ unit_weight, ) +from .date_unit import DateUnit # noqa: F401 from .instant_ import Instant # noqa: F401 from .period_ import Period # noqa: F401 diff --git a/openfisca_core/periods/config.py b/openfisca_core/periods/config.py index 6e0c698098..f81a55c122 100644 --- a/openfisca_core/periods/config.py +++ b/openfisca_core/periods/config.py @@ -1,15 +1,17 @@ import re -import typing +from typing import Dict -DAY = 'day' -MONTH = 'month' -YEAR = 'year' -ETERNITY = 'eternity' +from .date_unit import DateUnit + +DAY = DateUnit.DAY +MONTH = DateUnit.MONTH +YEAR = DateUnit.YEAR +ETERNITY = DateUnit.ETERNITY # Matches "2015", "2015-01", "2015-01-01" # Does not match "2015-13", "2015-12-32" INSTANT_PATTERN = re.compile(r"^\d{4}(-(0[1-9]|1[012]))?(-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01]))?$") -date_by_instant_cache: typing.Dict = {} -str_by_instant_cache: typing.Dict = {} +date_by_instant_cache: Dict = {} +str_by_instant_cache: Dict = {} year_or_month_or_day_re = re.compile(r'(18|19|20)\d{2}(-(0?[1-9]|1[0-2])(-([0-2]?\d|3[0-1]))?)?$') diff --git a/openfisca_core/periods/date_unit.py b/openfisca_core/periods/date_unit.py new file mode 100644 index 0000000000..8faac750ab --- /dev/null +++ b/openfisca_core/periods/date_unit.py @@ -0,0 +1,54 @@ +from strenum import StrEnum + + +class DateUnit(StrEnum): + """The date units of a rule system. + + Examples: + >>> repr(DateUnit) + "" + + >>> repr(DateUnit.DAY) + "" + + >>> str(DateUnit.DAY) + 'day' + + >>> dict([(DateUnit.DAY, DateUnit.DAY.value)]) + {: 'day'} + + >>> list(DateUnit) + [, , ...] + + >>> len(DateUnit) + 4 + + >>> DateUnit["DAY"] + + + >>> DateUnit(DateUnit.DAY) + + + >>> DateUnit.DAY in DateUnit + True + + >>> "day" in list(DateUnit) + True + + >>> DateUnit.DAY == "day" + True + + >>> DateUnit.DAY.name + 'DAY' + + >>> DateUnit.DAY.value + 'day' + + .. versionadded:: 35.9.0 + + """ + + DAY = "day" + MONTH = "month" + YEAR = "year" + ETERNITY = "eternity" diff --git a/openfisca_core/periods/helpers.py b/openfisca_core/periods/helpers.py index 2002774aa1..5c016e3879 100644 --- a/openfisca_core/periods/helpers.py +++ b/openfisca_core/periods/helpers.py @@ -3,6 +3,7 @@ from typing import Dict, NoReturn, Optional from . import config +from .date_unit import DateUnit from .instant_ import Instant from .period_ import Period @@ -37,7 +38,7 @@ def instant(instant) -> Optional[Instant]: >>> instant(Instant((2021, 9, 16))) Instant((2021, 9, 16)) - >>> instant(Period(("year", Instant((2021, 9, 16)), 1))) + >>> instant(Period((DateUnit.YEAR, Instant((2021, 9, 16)), 1))) Instant((2021, 9, 16)) >>> instant(2021) @@ -115,35 +116,35 @@ def period(value) -> Period: :exc:`ValueError`: When the arguments were invalid, like "2021-32-13". Examples: - >>> period(Period(("year", Instant((2021, 1, 1)), 1))) - Period(('year', Instant((2021, 1, 1)), 1)) + >>> period(Period((DateUnit.YEAR, Instant((2021, 1, 1)), 1))) + Period((, Instant((2021, 1, 1)), 1)) >>> period(Instant((2021, 1, 1))) - Period(('day', Instant((2021, 1, 1)), 1)) + Period((, Instant((2021, 1, 1)), 1)) - >>> period("eternity") - Period(('eternity', Instant((1, 1, 1)), inf)) + >>> period(DateUnit.ETERNITY) + Period((, Instant((1, 1, 1)), inf)) >>> period(2021) - Period(('year', Instant((2021, 1, 1)), 1)) + Period((, Instant((2021, 1, 1)), 1)) >>> period("2014") - Period(('year', Instant((2014, 1, 1)), 1)) + Period((, Instant((2014, 1, 1)), 1)) >>> period("year:2014") - Period(('year', Instant((2014, 1, 1)), 1)) + Period((, Instant((2014, 1, 1)), 1)) >>> period("month:2014-2") - Period(('month', Instant((2014, 2, 1)), 1)) + Period((, Instant((2014, 2, 1)), 1)) >>> period("year:2014-2") - Period(('year', Instant((2014, 2, 1)), 1)) + Period((, Instant((2014, 2, 1)), 1)) >>> period("day:2014-2-2") - Period(('day', Instant((2014, 2, 2)), 1)) + Period((, Instant((2014, 2, 2)), 1)) >>> period("day:2014-2-2:3") - Period(('day', Instant((2014, 2, 2)), 3)) + Period((, Instant((2014, 2, 2)), 3)) """ @@ -151,14 +152,14 @@ def period(value) -> Period: return value if isinstance(value, Instant): - return Period((config.DAY, value, 1)) + return Period((DateUnit.DAY, value, 1)) - if value == 'ETERNITY' or value == config.ETERNITY: - return Period(('eternity', instant(datetime.date.min), float("inf"))) + if value == 'ETERNITY' or value == DateUnit.ETERNITY: + return Period((DateUnit.ETERNITY, instant(datetime.date.min), float("inf"))) # check the type if isinstance(value, int): - return Period((config.YEAR, Instant((value, 1, 1)), 1)) + return Period((DateUnit.YEAR, Instant((value, 1, 1)), 1)) if not isinstance(value, str): _raise_error(value) @@ -175,9 +176,13 @@ def period(value) -> Period: # left-most component must be a valid unit unit = components[0] - if unit not in (config.DAY, config.MONTH, config.YEAR): + + if unit not in (DateUnit.DAY, DateUnit.MONTH, DateUnit.YEAR): _raise_error(value) + else: + unit = DateUnit(unit) + # middle component must be a valid iso period base_period = _parse_simple_period(components[1]) if not base_period: @@ -210,13 +215,13 @@ def _parse_simple_period(value: str) -> Optional[Period]: Examples: >>> _parse_simple_period("2022") - Period(('year', Instant((2022, 1, 1)), 1)) + Period((, Instant((2022, 1, 1)), 1)) >>> _parse_simple_period("2022-02") - Period(('month', Instant((2022, 2, 1)), 1)) + Period((, Instant((2022, 2, 1)), 1)) >>> _parse_simple_period("2022-02-13") - Period(('day', Instant((2022, 2, 13)), 1)) + Period((, Instant((2022, 2, 13)), 1)) """ @@ -231,11 +236,11 @@ def _parse_simple_period(value: str) -> Optional[Period]: except ValueError: return None else: - return Period((config.DAY, Instant((date.year, date.month, date.day)), 1)) + return Period((DateUnit.DAY, Instant((date.year, date.month, date.day)), 1)) else: - return Period((config.MONTH, Instant((date.year, date.month, 1)), 1)) + return Period((DateUnit.MONTH, Instant((date.year, date.month, 1)), 1)) else: - return Period((config.YEAR, Instant((date.year, date.month, 1)), 1)) + return Period((DateUnit.YEAR, Instant((date.year, date.month, 1)), 1)) def _raise_error(value: str) -> NoReturn: @@ -244,7 +249,7 @@ def _raise_error(value: str) -> NoReturn: Examples: >>> _raise_error("Oi mate!") Traceback (most recent call last): - ValueError: Expected a period (eg. '2017', '2017-01', '2017-01-01', ...); got: 'Oi mate!'. + ValueError: Expected a period (eg. '2017', ...); got: 'Oi mate!'. """ @@ -270,11 +275,11 @@ def key_period_size(period: Period) -> str: Examples: >>> instant = Instant((2021, 9, 14)) - >>> period = Period(("day", instant, 1)) + >>> period = Period((DateUnit.DAY, instant, 1)) >>> key_period_size(period) '100_1' - >>> period = Period(("year", instant, 3)) + >>> period = Period((DateUnit.YEAR, instant, 3)) >>> key_period_size(period) '300_3' @@ -290,15 +295,15 @@ def unit_weights() -> Dict[str, int]: Examples: >>> unit_weights() - {'day': 100, ...} + {: 100, ...: 400} """ return { - config.DAY: 100, - config.MONTH: 200, - config.YEAR: 300, - config.ETERNITY: 400, + DateUnit.DAY: 100, + DateUnit.MONTH: 200, + DateUnit.YEAR: 300, + DateUnit.ETERNITY: 400, } @@ -306,7 +311,7 @@ def unit_weight(unit: str) -> int: """Retrieves a specific date unit weight. Examples: - >>> unit_weight("day") + >>> unit_weight(DateUnit.DAY) 100 """ diff --git a/openfisca_core/periods/instant_.py b/openfisca_core/periods/instant_.py index a9a5e32779..a5cf9e2591 100644 --- a/openfisca_core/periods/instant_.py +++ b/openfisca_core/periods/instant_.py @@ -3,13 +3,14 @@ from .. import periods from . import config +from .date_unit import DateUnit class Instant(tuple): """An instant in time (year, month, day). An :class:`.Instant` represents the most atomic and indivisible - legislation's time unit. + legislation's date unit. Current implementation considers this unit to be a day, so :obj:`instants <.Instant>` can be thought of as "day dates". @@ -118,15 +119,15 @@ def period(self, unit, size = 1): :exc:`AssertionError`: When ``size`` is not an unsigned :obj:`int`. Examples: - >>> Instant((2021, 9, 13)).period("year") - Period(('year', Instant((2021, 9, 13)), 1)) + >>> Instant((2021, 9, 13)).period(DateUnit.YEAR) + Period((, Instant((2021, 9, 13)), 1)) - >>> Instant((2021, 9, 13)).period("month", 2) - Period(('month', Instant((2021, 9, 13)), 2)) + >>> Instant((2021, 9, 13)).period(DateUnit.MONTH, 2) + Period((, Instant((2021, 9, 13)), 2)) """ - assert unit in (config.DAY, config.MONTH, config.YEAR), 'Invalid unit: {} of type {}'.format(unit, type(unit)) + assert unit in (DateUnit.DAY, DateUnit.MONTH, DateUnit.YEAR), 'Invalid unit: {} of type {}'.format(unit, type(unit)) assert isinstance(size, int) and size >= 1, 'Invalid size: {} of type {}'.format(size, type(size)) return periods.Period((unit, self, size)) @@ -146,37 +147,37 @@ def offset(self, offset, unit): ``last-of``, or any :obj:`int`. Examples: - >>> Instant((2020, 12, 31)).offset("first-of", "month") + >>> Instant((2020, 12, 31)).offset("first-of", DateUnit.MONTH) Instant((2020, 12, 1)) - >>> Instant((2020, 1, 1)).offset("last-of", "year") + >>> Instant((2020, 1, 1)).offset("last-of", DateUnit.YEAR) Instant((2020, 12, 31)) - >>> Instant((2020, 1, 1)).offset(1, "year") + >>> Instant((2020, 1, 1)).offset(1, DateUnit.YEAR) Instant((2021, 1, 1)) - >>> Instant((2020, 1, 1)).offset(-3, "day") + >>> Instant((2020, 1, 1)).offset(-3, DateUnit.DAY) Instant((2019, 12, 29)) """ year, month, day = self - assert unit in (config.DAY, config.MONTH, config.YEAR), 'Invalid unit: {} of type {}'.format(unit, type(unit)) + assert unit in (DateUnit.DAY, DateUnit.MONTH, DateUnit.YEAR), 'Invalid unit: {} of type {}'.format(unit, type(unit)) if offset == 'first-of': - if unit == config.MONTH: + if unit == DateUnit.MONTH: day = 1 - elif unit == config.YEAR: + elif unit == DateUnit.YEAR: month = 1 day = 1 elif offset == 'last-of': - if unit == config.MONTH: + if unit == DateUnit.MONTH: day = calendar.monthrange(year, month)[1] - elif unit == config.YEAR: + elif unit == DateUnit.YEAR: month = 12 day = 31 else: assert isinstance(offset, int), 'Invalid offset: {} of type {}'.format(offset, type(offset)) - if unit == config.DAY: + if unit == DateUnit.DAY: day += offset if offset < 0: while day < 1: @@ -194,7 +195,7 @@ def offset(self, offset, unit): month = 1 day -= month_last_day month_last_day = calendar.monthrange(year, month)[1] - elif unit == config.MONTH: + elif unit == DateUnit.MONTH: month += offset if offset < 0: while month < 1: @@ -207,7 +208,7 @@ def offset(self, offset, unit): month_last_day = calendar.monthrange(year, month)[1] if day > month_last_day: day = month_last_day - elif unit == config.YEAR: + elif unit == DateUnit.YEAR: year += offset # Handle february month of leap year. month_last_day = calendar.monthrange(year, month)[1] diff --git a/openfisca_core/periods/period_.py b/openfisca_core/periods/period_.py index d343e3df05..814860a026 100644 --- a/openfisca_core/periods/period_.py +++ b/openfisca_core/periods/period_.py @@ -2,7 +2,8 @@ import calendar -from . import config, helpers +from . import helpers +from .date_unit import DateUnit from .instant_ import Instant @@ -25,13 +26,13 @@ class Period(tuple): Examples: >>> instant = Instant((2021, 9, 1)) - >>> period = Period(("year", instant, 3)) + >>> period = Period((DateUnit.YEAR, instant, 3)) >>> repr(Period) "" >>> repr(period) - "Period(('year', Instant((2021, 9, 1)), 3))" + "Period((, Instant((2021, 9, 1)), 3))" >>> str(period) 'year:2021-09:3' @@ -41,10 +42,10 @@ class Period(tuple): ValueError: dictionary update sequence element #0 has length 3... >>> list(period) - ['year', Instant((2021, 9, 1)), 3] + [, Instant((2021, 9, 1)), 3] >>> period[0] - 'year' + >>> period[0] in period True @@ -52,29 +53,29 @@ class Period(tuple): >>> len(period) 3 - >>> period == Period(("year", instant, 3)) + >>> period == Period((DateUnit.YEAR, instant, 3)) True - >>> period != Period(("year", instant, 3)) + >>> period != Period((DateUnit.YEAR, instant, 3)) False - >>> period > Period(("year", instant, 3)) + >>> period > Period((DateUnit.YEAR, instant, 3)) False - >>> period < Period(("year", instant, 3)) + >>> period < Period((DateUnit.YEAR, instant, 3)) False - >>> period >= Period(("year", instant, 3)) + >>> period >= Period((DateUnit.YEAR, instant, 3)) True - >>> period <= Period(("year", instant, 3)) + >>> period <= Period((DateUnit.YEAR, instant, 3)) True >>> period.date Traceback (most recent call last): AssertionError: "date" is undefined for a period of size > 1 - >>> Period(("year", instant, 1)).date + >>> Period((DateUnit.YEAR, instant, 1)).date datetime.date(2021, 9, 1) >>> period.days @@ -96,28 +97,28 @@ class Period(tuple): Instant((2024, 8, 31)) >>> period.unit - 'year' + >>> period.last_3_months - Period(('month', Instant((2021, 6, 1)), 3)) + Period((, Instant((2021, 6, 1)), 3)) >>> period.last_month - Period(('month', Instant((2021, 8, 1)), 1)) + Period((, Instant((2021, 8, 1)), 1)) >>> period.last_year - Period(('year', Instant((2020, 1, 1)), 1)) + Period((, Instant((2020, 1, 1)), 1)) >>> period.n_2 - Period(('year', Instant((2019, 1, 1)), 1)) + Period((, Instant((2019, 1, 1)), 1)) >>> period.this_year - Period(('year', Instant((2021, 1, 1)), 1)) + Period((, Instant((2021, 1, 1)), 1)) >>> period.first_month - Period(('month', Instant((2021, 9, 1)), 1)) + Period((, Instant((2021, 9, 1)), 1)) >>> period.first_day - Period(('day', Instant((2021, 9, 1)), 1)) + Period((, Instant((2021, 9, 1)), 1)) """ @@ -126,26 +127,26 @@ def __repr__(self) -> str: def __str__(self) -> str: unit, start_instant, size = self - if unit == config.ETERNITY: + if unit == DateUnit.ETERNITY: return 'ETERNITY' year, month, day = start_instant # 1 year long period - if (unit == config.MONTH and size == 12 or unit == config.YEAR and size == 1): + if (unit == DateUnit.MONTH and size == 12 or unit == DateUnit.YEAR and size == 1): if month == 1: # civil year starting from january return str(year) else: # rolling year - return '{}:{}-{:02d}'.format(config.YEAR, year, month) + return '{}:{}-{:02d}'.format(DateUnit.YEAR, year, month) # simple month - if unit == config.MONTH and size == 1: + if unit == DateUnit.MONTH and size == 1: return '{}-{:02d}'.format(year, month) # several civil years - if unit == config.YEAR and month == 1: + if unit == DateUnit.YEAR and month == 1: return '{}:{}:{}'.format(unit, year, size) - if unit == config.DAY: + if unit == DateUnit.DAY: if size == 1: return '{}-{:02d}-{:02d}'.format(year, month, day) else: @@ -182,14 +183,14 @@ def intersection(self, start, stop): if intersection_start.day == 1 and intersection_start.month == 1 \ and intersection_stop.day == 31 and intersection_stop.month == 12: return self.__class__(( - 'year', + DateUnit.YEAR, intersection_start, intersection_stop.year - intersection_start.year + 1, )) if intersection_start.day == 1 and intersection_stop.day == calendar.monthrange(intersection_stop.year, intersection_stop.month)[1]: return self.__class__(( - 'month', + DateUnit.MONTH, intersection_start, ( (intersection_stop.year - intersection_start.year) * 12 @@ -199,7 +200,7 @@ def intersection(self, start, stop): ), )) return self.__class__(( - 'day', + DateUnit.DAY, intersection_start, (intersection_stop.date - intersection_start.date).days + 1, )) @@ -208,199 +209,199 @@ def get_subperiods(self, unit): """Return the list of periods of unit ``unit`` contained in self. Examples: - >>> period = Period(("year", Instant((2021, 1, 1)), 1)) - >>> period.get_subperiods("month") - [Period(('month', Instant((2021, 1, 1)), 1)),...2021, 12, 1)), 1))] + >>> period = Period((DateUnit.YEAR, Instant((2021, 1, 1)), 1)) + >>> period.get_subperiods(DateUnit.MONTH) + [Period((, Instant((2021, 1, 1)), 1)),...2021, 12, 1)), 1))] - >>> period = Period(("year", Instant((2021, 1, 1)), 2)) - >>> period.get_subperiods("year") - [Period(('year', Instant((2021, 1, 1)), 1)),...((2022, 1, 1)), 1))] + >>> period = Period((DateUnit.YEAR, Instant((2021, 1, 1)), 2)) + >>> period.get_subperiods(DateUnit.YEAR) + [Period((, Instant((2021, 1, 1)), 1)),...((2022, 1, 1)), 1))] """ if helpers.unit_weight(self.unit) < helpers.unit_weight(unit): raise ValueError('Cannot subdivide {0} into {1}'.format(self.unit, unit)) - if unit == config.YEAR: - return [self.this_year.offset(i, config.YEAR) for i in range(self.size)] + if unit == DateUnit.YEAR: + return [self.this_year.offset(i, DateUnit.YEAR) for i in range(self.size)] - if unit == config.MONTH: - return [self.first_month.offset(i, config.MONTH) for i in range(self.size_in_months)] + if unit == DateUnit.MONTH: + return [self.first_month.offset(i, DateUnit.MONTH) for i in range(self.size_in_months)] - if unit == config.DAY: - return [self.first_day.offset(i, config.DAY) for i in range(self.size_in_days)] + if unit == DateUnit.DAY: + return [self.first_day.offset(i, DateUnit.DAY) for i in range(self.size_in_days)] def offset(self, offset, unit = None): """Increment (or decrement) the given period with offset units. Examples: - >>> Period(("day", Instant((2021, 1, 1)), 365)).offset(1) - Period(('day', Instant((2021, 1, 2)), 365)) + >>> Period((DateUnit.DAY, Instant((2021, 1, 1)), 365)).offset(1) + Period((, Instant((2021, 1, 2)), 365)) - >>> Period(("day", Instant((2021, 1, 1)), 365)).offset(1, "day") - Period(('day', Instant((2021, 1, 2)), 365)) + >>> Period((DateUnit.DAY, Instant((2021, 1, 1)), 365)).offset(1, DateUnit.DAY) + Period((, Instant((2021, 1, 2)), 365)) - >>> Period(("day", Instant((2021, 1, 1)), 365)).offset(1, "month") - Period(('day', Instant((2021, 2, 1)), 365)) + >>> Period((DateUnit.DAY, Instant((2021, 1, 1)), 365)).offset(1, DateUnit.MONTH) + Period((, Instant((2021, 2, 1)), 365)) - >>> Period(("day", Instant((2021, 1, 1)), 365)).offset(1, "year") - Period(('day', Instant((2022, 1, 1)), 365)) + >>> Period((DateUnit.DAY, Instant((2021, 1, 1)), 365)).offset(1, DateUnit.YEAR) + Period((, Instant((2022, 1, 1)), 365)) - >>> Period(("month", Instant((2021, 1, 1)), 12)).offset(1) - Period(('month', Instant((2021, 2, 1)), 12)) + >>> Period((DateUnit.MONTH, Instant((2021, 1, 1)), 12)).offset(1) + Period((, Instant((2021, 2, 1)), 12)) - >>> Period(("month", Instant((2021, 1, 1)), 12)).offset(1, "day") - Period(('month', Instant((2021, 1, 2)), 12)) + >>> Period((DateUnit.MONTH, Instant((2021, 1, 1)), 12)).offset(1, DateUnit.DAY) + Period((, Instant((2021, 1, 2)), 12)) - >>> Period(("month", Instant((2021, 1, 1)), 12)).offset(1, "month") - Period(('month', Instant((2021, 2, 1)), 12)) + >>> Period((DateUnit.MONTH, Instant((2021, 1, 1)), 12)).offset(1, DateUnit.MONTH) + Period((, Instant((2021, 2, 1)), 12)) - >>> Period(("month", Instant((2021, 1, 1)), 12)).offset(1, "year") - Period(('month', Instant((2022, 1, 1)), 12)) + >>> Period((DateUnit.MONTH, Instant((2021, 1, 1)), 12)).offset(1, DateUnit.YEAR) + Period((, Instant((2022, 1, 1)), 12)) - >>> Period(("year", Instant((2021, 1, 1)), 1)).offset(1) - Period(('year', Instant((2022, 1, 1)), 1)) + >>> Period((DateUnit.YEAR, Instant((2021, 1, 1)), 1)).offset(1) + Period((, Instant((2022, 1, 1)), 1)) - >>> Period(("year", Instant((2021, 1, 1)), 1)).offset(1, "day") - Period(('year', Instant((2021, 1, 2)), 1)) + >>> Period((DateUnit.YEAR, Instant((2021, 1, 1)), 1)).offset(1, DateUnit.DAY) + Period((, Instant((2021, 1, 2)), 1)) - >>> Period(("year", Instant((2021, 1, 1)), 1)).offset(1, "month") - Period(('year', Instant((2021, 2, 1)), 1)) + >>> Period((DateUnit.YEAR, Instant((2021, 1, 1)), 1)).offset(1, DateUnit.MONTH) + Period((, Instant((2021, 2, 1)), 1)) - >>> Period(("year", Instant((2021, 1, 1)), 1)).offset(1, "year") - Period(('year', Instant((2022, 1, 1)), 1)) + >>> Period((DateUnit.YEAR, Instant((2021, 1, 1)), 1)).offset(1, DateUnit.YEAR) + Period((, Instant((2022, 1, 1)), 1)) - >>> Period(("day", Instant((2011, 2, 28)), 1)).offset(1) - Period(('day', Instant((2011, 3, 1)), 1)) + >>> Period((DateUnit.DAY, Instant((2011, 2, 28)), 1)).offset(1) + Period((, Instant((2011, 3, 1)), 1)) - >>> Period(("month", Instant((2011, 2, 28)), 1)).offset(1) - Period(('month', Instant((2011, 3, 28)), 1)) + >>> Period((DateUnit.MONTH, Instant((2011, 2, 28)), 1)).offset(1) + Period((, Instant((2011, 3, 28)), 1)) - >>> Period(("year", Instant((2011, 2, 28)), 1)).offset(1) - Period(('year', Instant((2012, 2, 28)), 1)) + >>> Period((DateUnit.YEAR, Instant((2011, 2, 28)), 1)).offset(1) + Period((, Instant((2012, 2, 28)), 1)) - >>> Period(("day", Instant((2011, 3, 1)), 1)).offset(-1) - Period(('day', Instant((2011, 2, 28)), 1)) + >>> Period((DateUnit.DAY, Instant((2011, 3, 1)), 1)).offset(-1) + Period((, Instant((2011, 2, 28)), 1)) - >>> Period(("month", Instant((2011, 3, 1)), 1)).offset(-1) - Period(('month', Instant((2011, 2, 1)), 1)) + >>> Period((DateUnit.MONTH, Instant((2011, 3, 1)), 1)).offset(-1) + Period((, Instant((2011, 2, 1)), 1)) - >>> Period(("year", Instant((2011, 3, 1)), 1)).offset(-1) - Period(('year', Instant((2010, 3, 1)), 1)) + >>> Period((DateUnit.YEAR, Instant((2011, 3, 1)), 1)).offset(-1) + Period((, Instant((2010, 3, 1)), 1)) - >>> Period(("day", Instant((2014, 1, 30)), 1)).offset(3) - Period(('day', Instant((2014, 2, 2)), 1)) + >>> Period((DateUnit.DAY, Instant((2014, 1, 30)), 1)).offset(3) + Period((, Instant((2014, 2, 2)), 1)) - >>> Period(("month", Instant((2014, 1, 30)), 1)).offset(3) - Period(('month', Instant((2014, 4, 30)), 1)) + >>> Period((DateUnit.MONTH, Instant((2014, 1, 30)), 1)).offset(3) + Period((, Instant((2014, 4, 30)), 1)) - >>> Period(("year", Instant((2014, 1, 30)), 1)).offset(3) - Period(('year', Instant((2017, 1, 30)), 1)) + >>> Period((DateUnit.YEAR, Instant((2014, 1, 30)), 1)).offset(3) + Period((, Instant((2017, 1, 30)), 1)) - >>> Period(("day", Instant((2021, 1, 1)), 365)).offset(-3) - Period(('day', Instant((2020, 12, 29)), 365)) + >>> Period((DateUnit.DAY, Instant((2021, 1, 1)), 365)).offset(-3) + Period((, Instant((2020, 12, 29)), 365)) - >>> Period(("month", Instant((2021, 1, 1)), 12)).offset(-3) - Period(('month', Instant((2020, 10, 1)), 12)) + >>> Period((DateUnit.MONTH, Instant((2021, 1, 1)), 12)).offset(-3) + Period((, Instant((2020, 10, 1)), 12)) - >>> Period(("year", Instant((2014, 1, 1)), 1)).offset(-3) - Period(('year', Instant((2011, 1, 1)), 1)) + >>> Period((DateUnit.YEAR, Instant((2014, 1, 1)), 1)).offset(-3) + Period((, Instant((2011, 1, 1)), 1)) - >>> Period(("day", Instant((2014, 2, 3)), 1)).offset("first-of", "month") - Period(('day', Instant((2014, 2, 1)), 1)) + >>> Period((DateUnit.DAY, Instant((2014, 2, 3)), 1)).offset("first-of", DateUnit.MONTH) + Period((, Instant((2014, 2, 1)), 1)) - >>> Period(("day", Instant((2014, 2, 3)), 1)).offset("first-of", "year") - Period(('day', Instant((2014, 1, 1)), 1)) + >>> Period((DateUnit.DAY, Instant((2014, 2, 3)), 1)).offset("first-of", DateUnit.YEAR) + Period((, Instant((2014, 1, 1)), 1)) - >>> Period(("day", Instant((2014, 2, 3)), 4)).offset("first-of", "month") - Period(('day', Instant((2014, 2, 1)), 4)) + >>> Period((DateUnit.DAY, Instant((2014, 2, 3)), 4)).offset("first-of", DateUnit.MONTH) + Period((, Instant((2014, 2, 1)), 4)) - >>> Period(("day", Instant((2014, 2, 3)), 4)).offset("first-of", "year") - Period(('day', Instant((2014, 1, 1)), 4)) + >>> Period((DateUnit.DAY, Instant((2014, 2, 3)), 4)).offset("first-of", DateUnit.YEAR) + Period((, Instant((2014, 1, 1)), 4)) - >>> Period(("month", Instant((2014, 2, 3)), 1)).offset("first-of") - Period(('month', Instant((2014, 2, 1)), 1)) + >>> Period((DateUnit.MONTH, Instant((2014, 2, 3)), 1)).offset("first-of") + Period((, Instant((2014, 2, 1)), 1)) - >>> Period(("month", Instant((2014, 2, 3)), 1)).offset("first-of", "month") - Period(('month', Instant((2014, 2, 1)), 1)) + >>> Period((DateUnit.MONTH, Instant((2014, 2, 3)), 1)).offset("first-of", DateUnit.MONTH) + Period((, Instant((2014, 2, 1)), 1)) - >>> Period(("month", Instant((2014, 2, 3)), 1)).offset("first-of", "year") - Period(('month', Instant((2014, 1, 1)), 1)) + >>> Period((DateUnit.MONTH, Instant((2014, 2, 3)), 1)).offset("first-of", DateUnit.YEAR) + Period((, Instant((2014, 1, 1)), 1)) - >>> Period(("month", Instant((2014, 2, 3)), 4)).offset("first-of") - Period(('month', Instant((2014, 2, 1)), 4)) + >>> Period((DateUnit.MONTH, Instant((2014, 2, 3)), 4)).offset("first-of") + Period((, Instant((2014, 2, 1)), 4)) - >>> Period(("month", Instant((2014, 2, 3)), 4)).offset("first-of", "month") - Period(('month', Instant((2014, 2, 1)), 4)) + >>> Period((DateUnit.MONTH, Instant((2014, 2, 3)), 4)).offset("first-of", DateUnit.MONTH) + Period((, Instant((2014, 2, 1)), 4)) - >>> Period(("month", Instant((2014, 2, 3)), 4)).offset("first-of", "year") - Period(('month', Instant((2014, 1, 1)), 4)) + >>> Period((DateUnit.MONTH, Instant((2014, 2, 3)), 4)).offset("first-of", DateUnit.YEAR) + Period((, Instant((2014, 1, 1)), 4)) - >>> Period(("year", Instant((2014, 1, 30)), 1)).offset("first-of") - Period(('year', Instant((2014, 1, 1)), 1)) + >>> Period((DateUnit.YEAR, Instant((2014, 1, 30)), 1)).offset("first-of") + Period((, Instant((2014, 1, 1)), 1)) - >>> Period(("year", Instant((2014, 1, 30)), 1)).offset("first-of", "month") - Period(('year', Instant((2014, 1, 1)), 1)) + >>> Period((DateUnit.YEAR, Instant((2014, 1, 30)), 1)).offset("first-of", DateUnit.MONTH) + Period((, Instant((2014, 1, 1)), 1)) - >>> Period(("year", Instant((2014, 1, 30)), 1)).offset("first-of", "year") - Period(('year', Instant((2014, 1, 1)), 1)) + >>> Period((DateUnit.YEAR, Instant((2014, 1, 30)), 1)).offset("first-of", DateUnit.YEAR) + Period((, Instant((2014, 1, 1)), 1)) - >>> Period(("year", Instant((2014, 2, 3)), 1)).offset("first-of") - Period(('year', Instant((2014, 1, 1)), 1)) + >>> Period((DateUnit.YEAR, Instant((2014, 2, 3)), 1)).offset("first-of") + Period((, Instant((2014, 1, 1)), 1)) - >>> Period(("year", Instant((2014, 2, 3)), 1)).offset("first-of", "month") - Period(('year', Instant((2014, 2, 1)), 1)) + >>> Period((DateUnit.YEAR, Instant((2014, 2, 3)), 1)).offset("first-of", DateUnit.MONTH) + Period((, Instant((2014, 2, 1)), 1)) - >>> Period(("year", Instant((2014, 2, 3)), 1)).offset("first-of", "year") - Period(('year', Instant((2014, 1, 1)), 1)) + >>> Period((DateUnit.YEAR, Instant((2014, 2, 3)), 1)).offset("first-of", DateUnit.YEAR) + Period((, Instant((2014, 1, 1)), 1)) - >>> Period(("day", Instant((2014, 2, 3)), 1)).offset("last-of", "month") - Period(('day', Instant((2014, 2, 28)), 1)) + >>> Period((DateUnit.DAY, Instant((2014, 2, 3)), 1)).offset("last-of", DateUnit.MONTH) + Period((, Instant((2014, 2, 28)), 1)) - >>> Period(("day", Instant((2014, 2, 3)), 1)).offset("last-of", "year") - Period(('day', Instant((2014, 12, 31)), 1)) + >>> Period((DateUnit.DAY, Instant((2014, 2, 3)), 1)).offset("last-of", DateUnit.YEAR) + Period((, Instant((2014, 12, 31)), 1)) - >>> Period(("day", Instant((2014, 2, 3)), 4)).offset("last-of", "month") - Period(('day', Instant((2014, 2, 28)), 4)) + >>> Period((DateUnit.DAY, Instant((2014, 2, 3)), 4)).offset("last-of", DateUnit.MONTH) + Period((, Instant((2014, 2, 28)), 4)) - >>> Period(("day", Instant((2014, 2, 3)), 4)).offset("last-of", "year") - Period(('day', Instant((2014, 12, 31)), 4)) + >>> Period((DateUnit.DAY, Instant((2014, 2, 3)), 4)).offset("last-of", DateUnit.YEAR) + Period((, Instant((2014, 12, 31)), 4)) - >>> Period(("month", Instant((2014, 2, 3)), 1)).offset("last-of") - Period(('month', Instant((2014, 2, 28)), 1)) + >>> Period((DateUnit.MONTH, Instant((2014, 2, 3)), 1)).offset("last-of") + Period((, Instant((2014, 2, 28)), 1)) - >>> Period(("month", Instant((2014, 2, 3)), 1)).offset("last-of", "month") - Period(('month', Instant((2014, 2, 28)), 1)) + >>> Period((DateUnit.MONTH, Instant((2014, 2, 3)), 1)).offset("last-of", DateUnit.MONTH) + Period((, Instant((2014, 2, 28)), 1)) - >>> Period(("month", Instant((2014, 2, 3)), 1)).offset("last-of", "year") - Period(('month', Instant((2014, 12, 31)), 1)) + >>> Period((DateUnit.MONTH, Instant((2014, 2, 3)), 1)).offset("last-of", DateUnit.YEAR) + Period((, Instant((2014, 12, 31)), 1)) - >>> Period(("month", Instant((2014, 2, 3)), 4)).offset("last-of") - Period(('month', Instant((2014, 2, 28)), 4)) + >>> Period((DateUnit.MONTH, Instant((2014, 2, 3)), 4)).offset("last-of") + Period((, Instant((2014, 2, 28)), 4)) - >>> Period(("month", Instant((2014, 2, 3)), 4)).offset("last-of", "month") - Period(('month', Instant((2014, 2, 28)), 4)) + >>> Period((DateUnit.MONTH, Instant((2014, 2, 3)), 4)).offset("last-of", DateUnit.MONTH) + Period((, Instant((2014, 2, 28)), 4)) - >>> Period(("month", Instant((2014, 2, 3)), 4)).offset("last-of", "year") - Period(('month', Instant((2014, 12, 31)), 4)) + >>> Period((DateUnit.MONTH, Instant((2014, 2, 3)), 4)).offset("last-of", DateUnit.YEAR) + Period((, Instant((2014, 12, 31)), 4)) - >>> Period(("year", Instant((2014, 2, 3)), 1)).offset("last-of") - Period(('year', Instant((2014, 12, 31)), 1)) + >>> Period((DateUnit.YEAR, Instant((2014, 2, 3)), 1)).offset("last-of") + Period((, Instant((2014, 12, 31)), 1)) - >>> Period(("year", Instant((2014, 1, 1)), 1)).offset("last-of", "month") - Period(('year', Instant((2014, 1, 31)), 1)) + >>> Period((DateUnit.YEAR, Instant((2014, 1, 1)), 1)).offset("last-of", DateUnit.MONTH) + Period((, Instant((2014, 1, 31)), 1)) - >>> Period(("year", Instant((2014, 2, 3)), 1)).offset("last-of", "year") - Period(('year', Instant((2014, 12, 31)), 1)) + >>> Period((DateUnit.YEAR, Instant((2014, 2, 3)), 1)).offset("last-of", DateUnit.YEAR) + Period((, Instant((2014, 12, 31)), 1)) - >>> Period(("year", Instant((2014, 2, 3)), 1)).offset("last-of") - Period(('year', Instant((2014, 12, 31)), 1)) + >>> Period((DateUnit.YEAR, Instant((2014, 2, 3)), 1)).offset("last-of") + Period((, Instant((2014, 12, 31)), 1)) - >>> Period(("year", Instant((2014, 2, 3)), 1)).offset("last-of", "month") - Period(('year', Instant((2014, 2, 28)), 1)) + >>> Period((DateUnit.YEAR, Instant((2014, 2, 3)), 1)).offset("last-of", DateUnit.MONTH) + Period((, Instant((2014, 2, 28)), 1)) - >>> Period(("year", Instant((2014, 2, 3)), 1)).offset("last-of", "year") - Period(('year', Instant((2014, 12, 31)), 1)) + >>> Period((DateUnit.YEAR, Instant((2014, 2, 3)), 1)).offset("last-of", DateUnit.YEAR) + Period((, Instant((2014, 12, 31)), 1)) """ @@ -425,9 +426,9 @@ def size(self): def size_in_months(self): """Return the size of the period in months.""" - if (self[0] == config.MONTH): + if (self[0] == DateUnit.MONTH): return self[2] - if(self[0] == config.YEAR): + if(self[0] == DateUnit.YEAR): return self[2] * 12 raise ValueError("Cannot calculate number of months in {0}".format(self[0])) @@ -437,10 +438,10 @@ def size_in_days(self): unit, instant, length = self - if unit == config.DAY: + if unit == DateUnit.DAY: return length - if unit in [config.MONTH, config.YEAR]: - last_day = self.start.offset(length, unit).offset(-1, config.DAY) + if unit in [DateUnit.MONTH, DateUnit.YEAR]: + last_day = self.start.offset(length, unit).offset(-1, DateUnit.DAY) return (last_day.date - self.start.date).days + 1 raise ValueError("Cannot calculate number of days in {0}".format(unit)) @@ -456,40 +457,40 @@ def stop(self) -> Instant: """Return the last day of the period as an Instant instance. Examples: - >>> Period(("year", Instant((2022, 1, 1)), 1)).stop + >>> Period((DateUnit.YEAR, Instant((2022, 1, 1)), 1)).stop Instant((2022, 12, 31)) - >>> Period(("month", Instant((2022, 1, 1)), 12)).stop + >>> Period((DateUnit.MONTH, Instant((2022, 1, 1)), 12)).stop Instant((2022, 12, 31)) - >>> Period(("day", Instant((2022, 1, 1)), 365)).stop + >>> Period((DateUnit.DAY, Instant((2022, 1, 1)), 365)).stop Instant((2022, 12, 31)) - >>> Period(("year", Instant((2012, 2, 29)), 1)).stop + >>> Period((DateUnit.YEAR, Instant((2012, 2, 29)), 1)).stop Instant((2013, 2, 28)) - >>> Period(("month", Instant((2012, 2, 29)), 1)).stop + >>> Period((DateUnit.MONTH, Instant((2012, 2, 29)), 1)).stop Instant((2012, 3, 28)) - >>> Period(("day", Instant((2012, 2, 29)), 1)).stop + >>> Period((DateUnit.DAY, Instant((2012, 2, 29)), 1)).stop Instant((2012, 2, 29)) - >>> Period(("year", Instant((2012, 2, 29)), 2)).stop + >>> Period((DateUnit.YEAR, Instant((2012, 2, 29)), 2)).stop Instant((2014, 2, 28)) - >>> Period(("month", Instant((2012, 2, 29)), 2)).stop + >>> Period((DateUnit.MONTH, Instant((2012, 2, 29)), 2)).stop Instant((2012, 4, 28)) - >>> Period(("day", Instant((2012, 2, 29)), 2)).stop + >>> Period((DateUnit.DAY, Instant((2012, 2, 29)), 2)).stop Instant((2012, 3, 1)) """ unit, start_instant, size = self year, month, day = start_instant - if unit == config.ETERNITY: + if unit == DateUnit.ETERNITY: return Instant((float("inf"), float("inf"), float("inf"))) - if unit == 'day': + if unit == DateUnit.DAY: if size > 1: day += size - 1 month_last_day = calendar.monthrange(year, month)[1] @@ -501,13 +502,13 @@ def stop(self) -> Instant: day -= month_last_day month_last_day = calendar.monthrange(year, month)[1] else: - if unit == 'month': + if unit == DateUnit.MONTH: month += size while month > 12: year += 1 month -= 12 else: - assert unit == 'year', 'Invalid unit: {} of type {}'.format(unit, type(unit)) + assert unit == DateUnit.YEAR, 'Invalid unit: {} of type {}'.format(unit, type(unit)) year += size day -= 1 if day < 1: @@ -534,7 +535,7 @@ def unit(self): @property def last_3_months(self): - return self.first_month.start.period('month', 3).offset(-3) + return self.first_month.start.period(DateUnit.MONTH, 3).offset(-3) @property def last_month(self): @@ -542,20 +543,20 @@ def last_month(self): @property def last_year(self): - return self.start.offset("first-of", "year").period('year').offset(-1) + return self.start.offset("first-of", DateUnit.YEAR).period(DateUnit.YEAR).offset(-1) @property def n_2(self): - return self.start.offset("first-of", "year").period('year').offset(-2) + return self.start.offset("first-of", DateUnit.YEAR).period(DateUnit.YEAR).offset(-2) @property def this_year(self): - return self.start.offset("first-of", "year").period('year') + return self.start.offset("first-of", DateUnit.YEAR).period(DateUnit.YEAR) @property def first_month(self): - return self.start.offset("first-of", "month").period('month') + return self.start.offset("first-of", DateUnit.MONTH).period(DateUnit.MONTH) @property def first_day(self): - return self.start.period('day') + return self.start.period(DateUnit.DAY) diff --git a/openfisca_core/periods/tests/test_helpers.py b/openfisca_core/periods/tests/test_helpers.py index 6dd09788b9..2095e940f3 100644 --- a/openfisca_core/periods/tests/test_helpers.py +++ b/openfisca_core/periods/tests/test_helpers.py @@ -3,14 +3,14 @@ import pytest from openfisca_core import periods -from openfisca_core.periods import Instant, Period, helpers +from openfisca_core.periods import DateUnit, Instant, Period, helpers @pytest.mark.parametrize("arg, expected", [ [None, None], [datetime.date(1, 1, 1), Instant((1, 1, 1))], [Instant((1, 1, 1)), Instant((1, 1, 1))], - [Period((periods.DAY, Instant((1, 1, 1)), 365)), Instant((1, 1, 1))], + [Period((DateUnit.DAY, Instant((1, 1, 1)), 365)), Instant((1, 1, 1))], [-1, Instant((-1, 1, 1))], [0, Instant((0, 1, 1))], [1, Instant((1, 1, 1))], @@ -24,7 +24,7 @@ [(None, None, None), Instant((None, None, None))], [(datetime.date(1, 1, 1),), Instant((datetime.date(1, 1, 1), 1, 1))], [(Instant((1, 1, 1)),), Instant((Instant((1, 1, 1)), 1, 1))], - [(Period((periods.DAY, Instant((1, 1, 1)), 365)),), Instant((Period((periods.DAY, Instant((1, 1, 1)), 365)), 1, 1))], + [(Period((DateUnit.DAY, Instant((1, 1, 1)), 365)),), Instant((Period((DateUnit.DAY, Instant((1, 1, 1)), 365)), 1, 1))], [(-1,), Instant((-1, 1, 1))], [(-1, -1), Instant((-1, -1, 1))], [(-1, -1, -1), Instant((-1, -1, -1))], @@ -39,8 +39,8 @@ def test_instant_with_a_valid_argument(arg, expected): @pytest.mark.parametrize("arg, error", [ - [periods.YEAR, ValueError], - [periods.ETERNITY, ValueError], + [DateUnit.YEAR, ValueError], + [DateUnit.ETERNITY, ValueError], ["1000-0", ValueError], ["1000-0-0", ValueError], ["1000-1", ValueError], @@ -94,39 +94,39 @@ def test_instant_date_with_an_invalid_argument(arg, error): @pytest.mark.parametrize("arg, expected", [ - ["eternity", Period((periods.ETERNITY, Instant((1, 1, 1)), float("inf")))], - ["ETERNITY", Period((periods.ETERNITY, Instant((1, 1, 1)), float("inf")))], - [periods.ETERNITY, Period((periods.ETERNITY, Instant((1, 1, 1)), float("inf")))], - [Instant((1, 1, 1)), Period((periods.DAY, Instant((1, 1, 1)), 1))], - [Period((periods.DAY, Instant((1, 1, 1)), 365)), Period((periods.DAY, Instant((1, 1, 1)), 365))], - [-1, Period((periods.YEAR, Instant((-1, 1, 1)), 1))], - [0, Period((periods.YEAR, Instant((0, 1, 1)), 1))], - [1, Period((periods.YEAR, Instant((1, 1, 1)), 1))], - [999, Period((periods.YEAR, Instant((999, 1, 1)), 1))], - [1000, Period((periods.YEAR, Instant((1000, 1, 1)), 1))], - ["1000", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], - ["1000-1", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], - ["1000-1-1", Period((periods.DAY, Instant((1000, 1, 1)), 1))], - ["1000-01", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], - ["1000-01-01", Period((periods.DAY, Instant((1000, 1, 1)), 1))], - ["1004-02-29", Period((periods.DAY, Instant((1004, 2, 29)), 1))], - ["year:1000", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], - ["year:1000-01", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], - ["year:1000-01-01", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], - ["year:1000:1", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], - ["year:1000-01:1", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], - ["year:1000-01-01:1", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], - ["year:1000:3", Period((periods.YEAR, Instant((1000, 1, 1)), 3))], - ["year:1000-01:3", Period((periods.YEAR, Instant((1000, 1, 1)), 3))], - ["month:1000-01-01:3", Period((periods.MONTH, Instant((1000, 1, 1)), 3))], - ["month:1000-01", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], - ["month:1000-01-01", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], - ["month:1000-01:1", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], - ["month:1000-01:3", Period((periods.MONTH, Instant((1000, 1, 1)), 3))], - ["month:1000-01-01:3", Period((periods.MONTH, Instant((1000, 1, 1)), 3))], - ["month:1000-01-01:3", Period((periods.MONTH, Instant((1000, 1, 1)), 3))], - ["day:1000-01-01", Period((periods.DAY, Instant((1000, 1, 1)), 1))], - ["day:1000-01-01:3", Period((periods.DAY, Instant((1000, 1, 1)), 3))], + ["eternity", Period((DateUnit.ETERNITY, Instant((1, 1, 1)), float("inf")))], + ["ETERNITY", Period((DateUnit.ETERNITY, Instant((1, 1, 1)), float("inf")))], + [DateUnit.ETERNITY, Period((DateUnit.ETERNITY, Instant((1, 1, 1)), float("inf")))], + [Instant((1, 1, 1)), Period((DateUnit.DAY, Instant((1, 1, 1)), 1))], + [Period((DateUnit.DAY, Instant((1, 1, 1)), 365)), Period((DateUnit.DAY, Instant((1, 1, 1)), 365))], + [-1, Period((DateUnit.YEAR, Instant((-1, 1, 1)), 1))], + [0, Period((DateUnit.YEAR, Instant((0, 1, 1)), 1))], + [1, Period((DateUnit.YEAR, Instant((1, 1, 1)), 1))], + [999, Period((DateUnit.YEAR, Instant((999, 1, 1)), 1))], + [1000, Period((DateUnit.YEAR, Instant((1000, 1, 1)), 1))], + ["1000", Period((DateUnit.YEAR, Instant((1000, 1, 1)), 1))], + ["1000-1", Period((DateUnit.MONTH, Instant((1000, 1, 1)), 1))], + ["1000-1-1", Period((DateUnit.DAY, Instant((1000, 1, 1)), 1))], + ["1000-01", Period((DateUnit.MONTH, Instant((1000, 1, 1)), 1))], + ["1000-01-01", Period((DateUnit.DAY, Instant((1000, 1, 1)), 1))], + ["1004-02-29", Period((DateUnit.DAY, Instant((1004, 2, 29)), 1))], + ["year:1000", Period((DateUnit.YEAR, Instant((1000, 1, 1)), 1))], + ["year:1000-01", Period((DateUnit.YEAR, Instant((1000, 1, 1)), 1))], + ["year:1000-01-01", Period((DateUnit.YEAR, Instant((1000, 1, 1)), 1))], + ["year:1000:1", Period((DateUnit.YEAR, Instant((1000, 1, 1)), 1))], + ["year:1000-01:1", Period((DateUnit.YEAR, Instant((1000, 1, 1)), 1))], + ["year:1000-01-01:1", Period((DateUnit.YEAR, Instant((1000, 1, 1)), 1))], + ["year:1000:3", Period((DateUnit.YEAR, Instant((1000, 1, 1)), 3))], + ["year:1000-01:3", Period((DateUnit.YEAR, Instant((1000, 1, 1)), 3))], + ["month:1000-01-01:3", Period((DateUnit.MONTH, Instant((1000, 1, 1)), 3))], + ["month:1000-01", Period((DateUnit.MONTH, Instant((1000, 1, 1)), 1))], + ["month:1000-01-01", Period((DateUnit.MONTH, Instant((1000, 1, 1)), 1))], + ["month:1000-01:1", Period((DateUnit.MONTH, Instant((1000, 1, 1)), 1))], + ["month:1000-01:3", Period((DateUnit.MONTH, Instant((1000, 1, 1)), 3))], + ["month:1000-01-01:3", Period((DateUnit.MONTH, Instant((1000, 1, 1)), 3))], + ["month:1000-01-01:3", Period((DateUnit.MONTH, Instant((1000, 1, 1)), 3))], + ["day:1000-01-01", Period((DateUnit.DAY, Instant((1000, 1, 1)), 1))], + ["day:1000-01-01:3", Period((DateUnit.DAY, Instant((1000, 1, 1)), 3))], ]) def test_period_with_a_valid_argument(arg, expected): assert periods.period(arg) == expected @@ -134,7 +134,7 @@ def test_period_with_a_valid_argument(arg, expected): @pytest.mark.parametrize("arg, error", [ [None, ValueError], - [periods.YEAR, ValueError], + [DateUnit.YEAR, ValueError], [datetime.date(1, 1, 1), ValueError], ["1000-0", ValueError], ["1000-13", ValueError], @@ -165,7 +165,7 @@ def test_period_with_a_valid_argument(arg, expected): [(None, None, None, None), ValueError], [(datetime.date(1, 1, 1),), ValueError], [(Instant((1, 1, 1)),), ValueError], - [(Period((periods.DAY, Instant((1, 1, 1)), 365)),), ValueError], + [(Period((DateUnit.DAY, Instant((1, 1, 1)), 365)),), ValueError], [(1,), ValueError], [(1, 1), ValueError], [(1, 1, 1), ValueError], @@ -186,12 +186,12 @@ def test_period_with_an_invalid_argument(arg, error): @pytest.mark.parametrize("arg, expected", [ ["1", None], ["999", None], - ["1000", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], - ["1000-1", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], - ["1000-01", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], - ["1000-1-1", Period((periods.DAY, Instant((1000, 1, 1)), 1))], - ["1000-01-1", Period((periods.DAY, Instant((1000, 1, 1)), 1))], - ["1000-01-01", Period((periods.DAY, Instant((1000, 1, 1)), 1))], + ["1000", Period((DateUnit.YEAR, Instant((1000, 1, 1)), 1))], + ["1000-1", Period((DateUnit.MONTH, Instant((1000, 1, 1)), 1))], + ["1000-01", Period((DateUnit.MONTH, Instant((1000, 1, 1)), 1))], + ["1000-1-1", Period((DateUnit.DAY, Instant((1000, 1, 1)), 1))], + ["1000-01-1", Period((DateUnit.DAY, Instant((1000, 1, 1)), 1))], + ["1000-01-01", Period((DateUnit.DAY, Instant((1000, 1, 1)), 1))], ["1000-01-99", None], ]) def test__parse_simple_period_with_a_valid_argument(arg, expected): @@ -199,12 +199,12 @@ def test__parse_simple_period_with_a_valid_argument(arg, expected): @pytest.mark.parametrize("arg, expected", [ - [Period((periods.DAY, Instant((1, 1, 1)), 365)), "100_365"], - [Period((periods.MONTH, Instant((1, 1, 1)), 12)), "200_12"], - [Period((periods.YEAR, Instant((1, 1, 1)), 2)), "300_2"], - [Period((periods.ETERNITY, Instant((1, 1, 1)), 1)), "400_1"], - [(periods.DAY, None, 1), "100_1"], - [(periods.MONTH, None, -1000), "200_-1000"], + [Period((DateUnit.DAY, Instant((1, 1, 1)), 365)), "100_365"], + [Period((DateUnit.MONTH, Instant((1, 1, 1)), 12)), "200_12"], + [Period((DateUnit.YEAR, Instant((1, 1, 1)), 2)), "300_2"], + [Period((DateUnit.ETERNITY, Instant((1, 1, 1)), 1)), "400_1"], + [(DateUnit.DAY, None, 1), "100_1"], + [(DateUnit.MONTH, None, -1000), "200_-1000"], ]) def test_key_period_size_with_a_valid_argument(arg, expected): assert periods.key_period_size(arg) == expected diff --git a/openfisca_core/periods/tests/test_period.py b/openfisca_core/periods/tests/test_period.py index 4aab4d3339..dbc846af26 100644 --- a/openfisca_core/periods/tests/test_period.py +++ b/openfisca_core/periods/tests/test_period.py @@ -1,47 +1,47 @@ import pytest from openfisca_core import periods -from openfisca_core.periods import Instant, Period +from openfisca_core.periods import DateUnit, Instant, Period @pytest.mark.parametrize("date_unit, instant, size, expected", [ - [periods.YEAR, Instant((2022, 1, 1)), 1, "2022"], - [periods.MONTH, Instant((2022, 1, 1)), 12, "2022"], - [periods.YEAR, Instant((2022, 3, 1)), 1, "year:2022-03"], - [periods.MONTH, Instant((2022, 3, 1)), 12, "year:2022-03"], - [periods.YEAR, Instant((2022, 1, 1)), 3, "year:2022:3"], - [periods.YEAR, Instant((2022, 1, 3)), 3, "year:2022:3"], + [DateUnit.YEAR, Instant((2022, 1, 1)), 1, "2022"], + [DateUnit.MONTH, Instant((2022, 1, 1)), 12, "2022"], + [DateUnit.YEAR, Instant((2022, 3, 1)), 1, "year:2022-03"], + [DateUnit.MONTH, Instant((2022, 3, 1)), 12, "year:2022-03"], + [DateUnit.YEAR, Instant((2022, 1, 1)), 3, "year:2022:3"], + [DateUnit.YEAR, Instant((2022, 1, 3)), 3, "year:2022:3"], ]) def test_str_with_years(date_unit, instant, size, expected): assert str(Period((date_unit, instant, size))) == expected @pytest.mark.parametrize("date_unit, instant, size, expected", [ - [periods.MONTH, Instant((2022, 1, 1)), 1, "2022-01"], - [periods.MONTH, Instant((2022, 1, 1)), 3, "month:2022-01:3"], - [periods.MONTH, Instant((2022, 3, 1)), 3, "month:2022-03:3"], + [DateUnit.MONTH, Instant((2022, 1, 1)), 1, "2022-01"], + [DateUnit.MONTH, Instant((2022, 1, 1)), 3, "month:2022-01:3"], + [DateUnit.MONTH, Instant((2022, 3, 1)), 3, "month:2022-03:3"], ]) def test_str_with_months(date_unit, instant, size, expected): assert str(Period((date_unit, instant, size))) == expected @pytest.mark.parametrize("date_unit, instant, size, expected", [ - [periods.DAY, Instant((2022, 1, 1)), 1, "2022-01-01"], - [periods.DAY, Instant((2022, 1, 1)), 3, "day:2022-01-01:3"], - [periods.DAY, Instant((2022, 3, 1)), 3, "day:2022-03-01:3"], + [DateUnit.DAY, Instant((2022, 1, 1)), 1, "2022-01-01"], + [DateUnit.DAY, Instant((2022, 1, 1)), 3, "day:2022-01-01:3"], + [DateUnit.DAY, Instant((2022, 3, 1)), 3, "day:2022-03-01:3"], ]) def test_str_with_days(date_unit, instant, size, expected): assert str(Period((date_unit, instant, size))) == expected @pytest.mark.parametrize("period, unit, length, first, last", [ - (periods.period('year:2014:2'), periods.YEAR, 2, periods.period('2014'), periods.period('2015')), - (periods.period(2017), periods.MONTH, 12, periods.period('2017-01'), periods.period('2017-12')), - (periods.period('year:2014:2'), periods.MONTH, 24, periods.period('2014-01'), periods.period('2015-12')), - (periods.period('month:2014-03:3'), periods.MONTH, 3, periods.period('2014-03'), periods.period('2014-05')), - (periods.period(2017), periods.DAY, 365, periods.period('2017-01-01'), periods.period('2017-12-31')), - (periods.period('year:2014:2'), periods.DAY, 730, periods.period('2014-01-01'), periods.period('2015-12-31')), - (periods.period('month:2014-03:3'), periods.DAY, 92, periods.period('2014-03-01'), periods.period('2014-05-31')), + (periods.period('year:2014:2'), DateUnit.YEAR, 2, periods.period('2014'), periods.period('2015')), + (periods.period(2017), DateUnit.MONTH, 12, periods.period('2017-01'), periods.period('2017-12')), + (periods.period('year:2014:2'), DateUnit.MONTH, 24, periods.period('2014-01'), periods.period('2015-12')), + (periods.period('month:2014-03:3'), DateUnit.MONTH, 3, periods.period('2014-03'), periods.period('2014-05')), + (periods.period(2017), DateUnit.DAY, 365, periods.period('2017-01-01'), periods.period('2017-12-31')), + (periods.period('year:2014:2'), DateUnit.DAY, 730, periods.period('2014-01-01'), periods.period('2015-12-31')), + (periods.period('month:2014-03:3'), DateUnit.DAY, 92, periods.period('2014-03-01'), periods.period('2014-05-31')), ]) def test_subperiods(period, unit, length, first, last): subperiods = period.get_subperiods(unit) @@ -51,13 +51,13 @@ def test_subperiods(period, unit, length, first, last): @pytest.mark.parametrize("date_unit, instant, size, expected", [ - [periods.MONTH, Instant((2022, 12, 1)), 1, 1], - [periods.MONTH, Instant((2012, 2, 3)), 1, 1], - [periods.MONTH, Instant((2022, 1, 3)), 3, 3], - [periods.MONTH, Instant((2012, 1, 3)), 3, 3], - [periods.YEAR, Instant((2022, 12, 1)), 1, 12], - [periods.YEAR, Instant((2012, 1, 1)), 1, 12], - [periods.YEAR, Instant((2022, 1, 1)), 2, 24], + [DateUnit.MONTH, Instant((2022, 12, 1)), 1, 1], + [DateUnit.MONTH, Instant((2012, 2, 3)), 1, 1], + [DateUnit.MONTH, Instant((2022, 1, 3)), 3, 3], + [DateUnit.MONTH, Instant((2012, 1, 3)), 3, 3], + [DateUnit.YEAR, Instant((2022, 12, 1)), 1, 12], + [DateUnit.YEAR, Instant((2012, 1, 1)), 1, 12], + [DateUnit.YEAR, Instant((2022, 1, 1)), 2, 24], ]) def test_day_size_in_months(date_unit, instant, size, expected): period = Period((date_unit, instant, size)) @@ -65,15 +65,15 @@ def test_day_size_in_months(date_unit, instant, size, expected): @pytest.mark.parametrize("date_unit, instant, size, expected", [ - [periods.DAY, Instant((2022, 12, 31)), 1, 1], - [periods.DAY, Instant((2022, 12, 31)), 3, 3], - [periods.MONTH, Instant((2022, 12, 1)), 1, 31], - [periods.MONTH, Instant((2012, 2, 3)), 1, 29], - [periods.MONTH, Instant((2022, 1, 3)), 3, 31 + 28 + 31], - [periods.MONTH, Instant((2012, 1, 3)), 3, 31 + 29 + 31], - [periods.YEAR, Instant((2022, 12, 1)), 1, 365], - [periods.YEAR, Instant((2012, 1, 1)), 1, 366], - [periods.YEAR, Instant((2022, 1, 1)), 2, 730], + [DateUnit.DAY, Instant((2022, 12, 31)), 1, 1], + [DateUnit.DAY, Instant((2022, 12, 31)), 3, 3], + [DateUnit.MONTH, Instant((2022, 12, 1)), 1, 31], + [DateUnit.MONTH, Instant((2012, 2, 3)), 1, 29], + [DateUnit.MONTH, Instant((2022, 1, 3)), 3, 31 + 28 + 31], + [DateUnit.MONTH, Instant((2012, 1, 3)), 3, 31 + 29 + 31], + [DateUnit.YEAR, Instant((2022, 12, 1)), 1, 365], + [DateUnit.YEAR, Instant((2012, 1, 1)), 1, 366], + [DateUnit.YEAR, Instant((2022, 1, 1)), 2, 730], ]) def test_day_size_in_days(date_unit, instant, size, expected): period = Period((date_unit, instant, size)) diff --git a/openfisca_core/scripts/measure_performances.py b/openfisca_core/scripts/measure_performances.py index 1d84ddd585..a3a56c0aff 100644 --- a/openfisca_core/scripts/measure_performances.py +++ b/openfisca_core/scripts/measure_performances.py @@ -13,7 +13,7 @@ from numpy.core.defchararray import startswith from openfisca_core import periods, simulations -from openfisca_core.periods import ETERNITY +from openfisca_core.periods import DateUnit from openfisca_core.entities import build_entity from openfisca_core.variables import Variable from openfisca_core.taxbenefitsystems import TaxBenefitSystem @@ -82,7 +82,7 @@ class city_code(Variable): value_type = 'FixedStr' max_length = 5 entity = Famille - definition_period = ETERNITY + definition_period = DateUnit.ETERNITY label = """Code INSEE "city_code" de la commune de résidence de la famille""" @@ -115,7 +115,7 @@ class dom_tom(Variable): label = "La famille habite-t-elle les DOM-TOM ?" def formula(self, simulation, period): - period = period.start.period('year').offset('first-of') + period = period.start.period(DateUnit.YEAR).offset('first-of') city_code = simulation.calculate('city_code', period) return np.logical_or(startswith(city_code, '97'), startswith(city_code, '98')) @@ -126,7 +126,7 @@ class revenu_disponible(Variable): label = "Revenu disponible de l'individu" def formula(self, simulation, period): - period = period.start.period('year').offset('first-of') + period = period.start.period(DateUnit.YEAR).offset('first-of') rsa = simulation.calculate('rsa', period) salaire_imposable = simulation.calculate('salaire_imposable', period) return rsa + salaire_imposable * 0.7 @@ -138,17 +138,17 @@ class rsa(Variable): label = "RSA" def formula_2010_01_01(self, simulation, period): - period = period.start.period('month').offset('first-of') + period = period.start.period(DateUnit.MONTH).offset('first-of') salaire_imposable = simulation.calculate('salaire_imposable', period) return (salaire_imposable < 500) * 100.0 def formula_2011_01_01(self, simulation, period): - period = period.start.period('month').offset('first-of') + period = period.start.period(DateUnit.MONTH).offset('first-of') salaire_imposable = simulation.calculate('salaire_imposable', period) return (salaire_imposable < 500) * 200.0 def formula_2013_01_01(self, simulation, period): - period = period.start.period('month').offset('first-of') + period = period.start.period(DateUnit.MONTH).offset('first-of') salaire_imposable = simulation.calculate('salaire_imposable', period) return (salaire_imposable < 500) * 300 @@ -159,7 +159,7 @@ class salaire_imposable(Variable): label = "Salaire imposable" def formula(individu, period): - period = period.start.period('year').offset('first-of') + period = period.start.period(DateUnit.YEAR).offset('first-of') dom_tom = individu.famille('dom_tom', period) salaire_net = individu('salaire_net', period) return salaire_net * 0.9 - 100 * dom_tom @@ -171,7 +171,7 @@ class salaire_net(Variable): label = "Salaire net" def formula(self, simulation, period): - period = period.start.period('year').offset('first-of') + period = period.start.period(DateUnit.YEAR).offset('first-of') salaire_brut = simulation.calculate('salaire_brut', period) return salaire_brut * 0.8 diff --git a/openfisca_core/scripts/measure_performances_fancy_indexing.py b/openfisca_core/scripts/measure_performances_fancy_indexing.py index 894250ef54..69d8cea1be 100644 --- a/openfisca_core/scripts/measure_performances_fancy_indexing.py +++ b/openfisca_core/scripts/measure_performances_fancy_indexing.py @@ -1,9 +1,10 @@ # flake8: noqa T001 -import numpy as np import timeit + +import numpy as np + from openfisca_france import CountryTaxBenefitSystem -from openfisca_core.model_api import * # noqa analysis:ignore tbs = CountryTaxBenefitSystem() diff --git a/openfisca_core/simulations/simulation.py b/openfisca_core/simulations/simulation.py index 5dd2694292..8bd2497c50 100644 --- a/openfisca_core/simulations/simulation.py +++ b/openfisca_core/simulations/simulation.py @@ -6,7 +6,7 @@ from openfisca_core import commons, periods from openfisca_core.errors import CycleError, SpiralError from openfisca_core.indexed_enums import Enum, EnumArray -from openfisca_core.periods import Period +from openfisca_core.periods import DateUnit, Period from openfisca_core.tracers import FullTracer, SimpleTracer, TracingParameterNodeAtInstant from openfisca_core.warnings import TempfileWarning @@ -159,7 +159,7 @@ def calculate_add(self, variable_name, period): variable.definition_period )) - if variable.definition_period not in [periods.DAY, periods.MONTH, periods.YEAR]: + if variable.definition_period not in [DateUnit.DAY, DateUnit.MONTH, DateUnit.YEAR]: raise ValueError("Unable to sum constant variable '{}' over period {}: only variables defined daily, monthly, or yearly can be summed over time.".format( variable.name, period)) @@ -176,7 +176,7 @@ def calculate_divide(self, variable_name, period): period = periods.period(period) # Check that the requested period matches definition_period - if variable.definition_period != periods.YEAR: + if variable.definition_period != DateUnit.YEAR: raise ValueError("Unable to divide the value of '{}' over time on period {}: only variables defined yearly can be divided over time.".format( variable_name, period)) @@ -184,10 +184,10 @@ def calculate_divide(self, variable_name, period): if period.size != 1: raise ValueError("DIVIDE option can only be used for a one-year or a one-month requested period") - if period.unit == periods.MONTH: + if period.unit == DateUnit.MONTH: computation_period = period.this_year return self.calculate(variable_name, period = computation_period) / 12. - elif period.unit == periods.YEAR: + elif period.unit == DateUnit.YEAR: return self.calculate(variable_name, period) raise ValueError("Unable to divide the value of '{}' to match period {}.".format( @@ -237,16 +237,16 @@ def _check_period_consistency(self, period, variable): """ Check that a period matches the variable definition_period """ - if variable.definition_period == periods.ETERNITY: + if variable.definition_period == DateUnit.ETERNITY: return # For variables which values are constant in time, all periods are accepted - if variable.definition_period == periods.MONTH and period.unit != periods.MONTH: + if variable.definition_period == DateUnit.MONTH and period.unit != DateUnit.MONTH: raise ValueError("Unable to compute variable '{0}' for period {1}: '{0}' must be computed for a whole month. You can use the ADD option to sum '{0}' over the requested period, or change the requested period to 'period.first_month'.".format( variable.name, period )) - if variable.definition_period == periods.YEAR and period.unit != periods.YEAR: + if variable.definition_period == DateUnit.YEAR and period.unit != DateUnit.YEAR: raise ValueError("Unable to compute variable '{0}' for period {1}: '{0}' must be computed for a whole year. You can use the DIVIDE option to get an estimate of {0} by dividing the yearly value by 12, or change the requested period to 'period.this_year'.".format( variable.name, period @@ -256,7 +256,7 @@ def _check_period_consistency(self, period, variable): raise ValueError("Unable to compute variable '{0}' for period {1}: '{0}' must be computed for a whole {2}. You can use the ADD option to sum '{0}' over the requested period.".format( variable.name, period, - 'month' if variable.definition_period == periods.MONTH else 'year' + DateUnit.MONTH if variable.definition_period == DateUnit.MONTH else DateUnit.YEAR )) def _cast_formula_result(self, value, variable): diff --git a/openfisca_core/tools/simulation_dumper.py b/openfisca_core/tools/simulation_dumper.py index 4b5907c0ff..aff2377dcd 100644 --- a/openfisca_core/tools/simulation_dumper.py +++ b/openfisca_core/tools/simulation_dumper.py @@ -7,7 +7,7 @@ from openfisca_core.simulations import Simulation from openfisca_core.data_storage import OnDiskStorage -from openfisca_core.periods import ETERNITY +from openfisca_core.periods import DateUnit def dump_simulation(simulation, directory): @@ -116,7 +116,7 @@ def _restore_entity(population, directory): def _restore_holder(simulation, variable, directory): storage_dir = os.path.join(directory, variable) - is_variable_eternal = simulation.tax_benefit_system.get_variable(variable).definition_period == ETERNITY + is_variable_eternal = simulation.tax_benefit_system.get_variable(variable).definition_period == DateUnit.ETERNITY disk_storage = OnDiskStorage( storage_dir, is_eternal = is_variable_eternal, diff --git a/openfisca_core/variables/variable.py b/openfisca_core/variables/variable.py index 7d13e5da24..e5ada23407 100644 --- a/openfisca_core/variables/variable.py +++ b/openfisca_core/variables/variable.py @@ -9,7 +9,7 @@ from openfisca_core import periods, tools from openfisca_core.entities import Entity from openfisca_core.indexed_enums import Enum, EnumArray -from openfisca_core.periods import Period +from openfisca_core.periods import DateUnit, Period from . import config, helpers @@ -34,7 +34,7 @@ class Variable: .. attribute:: definition_period - `Period `_ the variable is defined for. Possible value: ``MONTH``, ``YEAR``, ``ETERNITY``. + `Period `_ the variable is defined for. Possible value: ``DateUnit.DAY``, ``DateUnit.MONTH``, ``DateUnit.YEAR``, ``DateUnit.ETERNITY``. .. attribute:: formulas @@ -115,7 +115,7 @@ def __init__(self, baseline_variable = None): else: self.default_value = self.set(attr, 'default_value', allowed_type = self.value_type, default = config.VALUE_TYPES[self.value_type].get('default')) self.entity = self.set(attr, 'entity', required = True, setter = self.set_entity) - self.definition_period = self.set(attr, 'definition_period', required = True, allowed_values = (periods.DAY, periods.MONTH, periods.YEAR, periods.ETERNITY)) + self.definition_period = self.set(attr, 'definition_period', required = True, allowed_values = (DateUnit.DAY, DateUnit.MONTH, DateUnit.YEAR, DateUnit.ETERNITY)) self.label = self.set(attr, 'label', allowed_type = str, setter = self.set_label) self.end = self.set(attr, 'end', allowed_type = str, setter = self.set_end) self.reference = self.set(attr, 'reference', setter = self.set_reference) diff --git a/setup.py b/setup.py index c377fc5034..5821d259fa 100644 --- a/setup.py +++ b/setup.py @@ -19,6 +19,7 @@ 'pytest >= 4.4.1, < 6.0.0', # For openfisca test 'PyYAML >= 3.10', 'sortedcontainers == 2.2.2', + 'StrEnum >= 0.4.8, < 0.5.0', # 3.11.x backport 'typing-extensions == 3.10.0.2', ] diff --git a/tests/core/parameters_fancy_indexing/test_fancy_indexing.py b/tests/core/parameters_fancy_indexing/test_fancy_indexing.py index d34eb00773..fcdd25c093 100644 --- a/tests/core/parameters_fancy_indexing/test_fancy_indexing.py +++ b/tests/core/parameters_fancy_indexing/test_fancy_indexing.py @@ -7,9 +7,10 @@ import pytest -from openfisca_core.tools import assert_near from openfisca_core.parameters import ParameterNode, Parameter, ParameterNotFound -from openfisca_core.model_api import * # noqa +from openfisca_core.indexed_enums import Enum +from openfisca_core.tools import assert_near + LOCAL_DIR = os.path.dirname(os.path.abspath(__file__)) diff --git a/tests/core/test_calculate_output.py b/tests/core/test_calculate_output.py index 6a11a27d84..d3eb5c6666 100644 --- a/tests/core/test_calculate_output.py +++ b/tests/core/test_calculate_output.py @@ -2,27 +2,28 @@ from openfisca_country_template import entities, situation_examples -from openfisca_core import periods, simulations, tools +from openfisca_core import simulations, tools +from openfisca_core.periods import DateUnit from openfisca_core.simulations import SimulationBuilder from openfisca_core.variables import Variable class simple_variable(Variable): entity = entities.Person - definition_period = periods.MONTH + definition_period = DateUnit.MONTH value_type = int class variable_with_calculate_output_add(Variable): entity = entities.Person - definition_period = periods.MONTH + definition_period = DateUnit.MONTH value_type = int calculate_output = simulations.calculate_output_add class variable_with_calculate_output_divide(Variable): entity = entities.Person - definition_period = periods.YEAR + definition_period = DateUnit.YEAR value_type = int calculate_output = simulations.calculate_output_divide diff --git a/tests/core/test_countries.py b/tests/core/test_countries.py index aeb4d762c7..f8f359fd2d 100644 --- a/tests/core/test_countries.py +++ b/tests/core/test_countries.py @@ -2,6 +2,7 @@ from openfisca_core import periods, populations, tools from openfisca_core.errors import VariableNameConflictError, VariableNotFoundError +from openfisca_core.periods import DateUnit from openfisca_core.simulations import SimulationBuilder from openfisca_core.variables import Variable @@ -102,7 +103,7 @@ def test_variable_with_reference(make_simulation, isolated_tax_benefit_system): assert result > 0 class disposable_income(Variable): - definition_period = periods.MONTH + definition_period = DateUnit.MONTH def formula(household, period): return household.empty_array() @@ -119,7 +120,7 @@ def test_variable_name_conflict(tax_benefit_system): class disposable_income(Variable): reference = "disposable_income" - definition_period = periods.MONTH + definition_period = DateUnit.MONTH def formula(household, period): return household.empty_array() diff --git a/tests/core/test_cycles.py b/tests/core/test_cycles.py index 1c4361ded2..0def1c5953 100644 --- a/tests/core/test_cycles.py +++ b/tests/core/test_cycles.py @@ -4,6 +4,7 @@ from openfisca_core import periods, tools from openfisca_core.errors import CycleError +from openfisca_core.periods import DateUnit from openfisca_core.simulations import SimulationBuilder from openfisca_core.variables import Variable @@ -22,7 +23,7 @@ def simulation(tax_benefit_system): class variable1(Variable): value_type = int entity = entities.Person - definition_period = periods.MONTH + definition_period = DateUnit.MONTH def formula(person, period): return person('variable2', period) @@ -31,7 +32,7 @@ def formula(person, period): class variable2(Variable): value_type = int entity = entities.Person - definition_period = periods.MONTH + definition_period = DateUnit.MONTH def formula(person, period): return person('variable1', period) @@ -41,7 +42,7 @@ def formula(person, period): class variable3(Variable): value_type = int entity = entities.Person - definition_period = periods.MONTH + definition_period = DateUnit.MONTH def formula(person, period): return person('variable4', period.last_month) @@ -50,7 +51,7 @@ def formula(person, period): class variable4(Variable): value_type = int entity = entities.Person - definition_period = periods.MONTH + definition_period = DateUnit.MONTH def formula(person, period): return person('variable3', period) @@ -61,7 +62,7 @@ def formula(person, period): class variable5(Variable): value_type = int entity = entities.Person - definition_period = periods.MONTH + definition_period = DateUnit.MONTH def formula(person, period): variable6 = person('variable6', period.last_month) @@ -71,7 +72,7 @@ def formula(person, period): class variable6(Variable): value_type = int entity = entities.Person - definition_period = periods.MONTH + definition_period = DateUnit.MONTH def formula(person, period): variable5 = person('variable5', period) @@ -81,7 +82,7 @@ def formula(person, period): class variable7(Variable): value_type = int entity = entities.Person - definition_period = periods.MONTH + definition_period = DateUnit.MONTH def formula(person, period): variable5 = person('variable5', period) @@ -92,7 +93,7 @@ def formula(person, period): class cotisation(Variable): value_type = int entity = entities.Person - definition_period = periods.MONTH + definition_period = DateUnit.MONTH def formula(person, period): if period.start.month == 12: diff --git a/tests/core/test_formulas.py b/tests/core/test_formulas.py index 8851671755..527fb21d2a 100644 --- a/tests/core/test_formulas.py +++ b/tests/core/test_formulas.py @@ -2,7 +2,8 @@ from openfisca_country_template import entities -from openfisca_core import commons, periods +from openfisca_core import commons +from openfisca_core.periods import DateUnit from openfisca_core.simulations import SimulationBuilder from openfisca_core.variables import Variable @@ -12,14 +13,14 @@ class choice(Variable): value_type = int entity = entities.Person - definition_period = periods.MONTH + definition_period = DateUnit.MONTH class uses_multiplication(Variable): value_type = int entity = entities.Person label = 'Variable with formula that uses multiplication' - definition_period = periods.MONTH + definition_period = DateUnit.MONTH def formula(person, period): choice = person('choice', period) @@ -31,7 +32,7 @@ class returns_scalar(Variable): value_type = int entity = entities.Person label = 'Variable with formula that returns a scalar value' - definition_period = periods.MONTH + definition_period = DateUnit.MONTH def formula(person, period): return 666 @@ -41,7 +42,7 @@ class uses_switch(Variable): value_type = int entity = entities.Person label = 'Variable with formula that uses switch' - definition_period = periods.MONTH + definition_period = DateUnit.MONTH def formula(person, period): choice = person('choice', period) @@ -106,7 +107,7 @@ def test_group_encapsulation(): """ from openfisca_core.taxbenefitsystems import TaxBenefitSystem from openfisca_core.entities import build_entity - from openfisca_core.periods import ETERNITY + from openfisca_core.periods import DateUnit person_entity = build_entity( key="person", @@ -143,12 +144,12 @@ def test_group_encapsulation(): class household_level_variable(Variable): value_type = int entity = household_entity - definition_period = ETERNITY + definition_period = DateUnit.ETERNITY class projected_family_level_variable(Variable): value_type = int entity = family_entity - definition_period = ETERNITY + definition_period = DateUnit.ETERNITY def formula(family, period): return family.household("household_level_variable", period) diff --git a/tests/core/test_holders.py b/tests/core/test_holders.py index 907aefceb5..231222c465 100644 --- a/tests/core/test_holders.py +++ b/tests/core/test_holders.py @@ -8,6 +8,7 @@ from openfisca_core import holders, periods, tools from openfisca_core.errors import PeriodMismatchError from openfisca_core.memory_config import MemoryConfig +from openfisca_core.periods import DateUnit from openfisca_core.simulations import SimulationBuilder from openfisca_core.holders import Holder @@ -89,9 +90,9 @@ def test_permanent_variable_filled(single): simulation = single holder = simulation.person.get_holder('birth') value = numpy.asarray(['1980-01-01'], dtype = holder.variable.dtype) - holder.set_input(periods.period(periods.ETERNITY), value) + holder.set_input(periods.period(DateUnit.ETERNITY), value) assert holder.get_array(None) == value - assert holder.get_array(periods.ETERNITY) == value + assert holder.get_array(DateUnit.ETERNITY) == value assert holder.get_array('2016-01') == value diff --git a/tests/core/test_opt_out_cache.py b/tests/core/test_opt_out_cache.py index b4eab3e5a5..c28322f468 100644 --- a/tests/core/test_opt_out_cache.py +++ b/tests/core/test_opt_out_cache.py @@ -3,7 +3,7 @@ from openfisca_country_template.entities import Person from openfisca_core import periods -from openfisca_core.periods import MONTH +from openfisca_core.periods import DateUnit from openfisca_core.variables import Variable @@ -14,14 +14,14 @@ class input(Variable): value_type = int entity = Person label = "Input variable" - definition_period = MONTH + definition_period = DateUnit.MONTH class intermediate(Variable): value_type = int entity = Person label = "Intermediate result that don't need to be cached" - definition_period = MONTH + definition_period = DateUnit.MONTH def formula(person, period): return person('input', period) @@ -31,7 +31,7 @@ class output(Variable): value_type = int entity = Person label = 'Output variable' - definition_period = MONTH + definition_period = DateUnit.MONTH def formula(person, period): return person('intermediate', period) diff --git a/tests/core/test_projectors.py b/tests/core/test_projectors.py index 32635e2571..955a2e627c 100644 --- a/tests/core/test_projectors.py +++ b/tests/core/test_projectors.py @@ -1,8 +1,11 @@ +import numpy as np + +from openfisca_core.entities import build_entity +from openfisca_core.indexed_enums import Enum +from openfisca_core.periods import DateUnit from openfisca_core.simulations.simulation_builder import SimulationBuilder from openfisca_core.taxbenefitsystems import TaxBenefitSystem -from openfisca_core.entities import build_entity -from openfisca_core.model_api import Enum, Variable, ETERNITY -import numpy as np +from openfisca_core.variables import Variable def test_shortcut_to_containing_entity_provided(): @@ -125,14 +128,14 @@ class household_enum_variable(Variable): possible_values = enum default_value = enum.FIRST_OPTION entity = household - definition_period = ETERNITY + definition_period = DateUnit.ETERNITY class projected_enum_variable(Variable): value_type = Enum possible_values = enum default_value = enum.FIRST_OPTION entity = person - definition_period = ETERNITY + definition_period = DateUnit.ETERNITY def formula(person, period): return person.household("household_enum_variable", period) @@ -194,7 +197,7 @@ class household_projected_variable(Variable): possible_values = enum default_value = enum.FIRST_OPTION entity = household - definition_period = ETERNITY + definition_period = DateUnit.ETERNITY def formula(household, period): return household.value_from_first_person(household.members("person_enum_variable", period)) @@ -204,7 +207,7 @@ class person_enum_variable(Variable): possible_values = enum default_value = enum.FIRST_OPTION entity = person - definition_period = ETERNITY + definition_period = DateUnit.ETERNITY system.add_variables(household_projected_variable, person_enum_variable) @@ -275,14 +278,14 @@ class household_level_variable(Variable): possible_values = enum default_value = enum.FIRST_OPTION entity = household_entity - definition_period = ETERNITY + definition_period = DateUnit.ETERNITY class projected_family_level_variable(Variable): value_type = Enum possible_values = enum default_value = enum.FIRST_OPTION entity = family_entity - definition_period = ETERNITY + definition_period = DateUnit.ETERNITY def formula(family, period): return family.household("household_level_variable", period) @@ -290,7 +293,7 @@ def formula(family, period): class decoded_projected_family_level_variable(Variable): value_type = str entity = family_entity - definition_period = ETERNITY + definition_period = DateUnit.ETERNITY def formula(family, period): return family.household("household_level_variable", period).decode_to_str() diff --git a/tests/core/test_reforms.py b/tests/core/test_reforms.py index 8735cee18f..de2a47fc7c 100644 --- a/tests/core/test_reforms.py +++ b/tests/core/test_reforms.py @@ -2,12 +2,15 @@ import pytest -from openfisca_core import periods +from openfisca_country_template.entities import Household, Person + +from openfisca_core import holders, periods, simulations +from openfisca_core.parameters import ValuesHistory, ParameterNode +from openfisca_core.periods import DateUnit from openfisca_core.periods import Instant +from openfisca_core.reforms import Reform from openfisca_core.tools import assert_near -from openfisca_core.parameters import ValuesHistory, ParameterNode -from openfisca_country_template.entities import Household, Person -from openfisca_core.model_api import * # noqa analysis:ignore +from openfisca_core.variables import Variable class goes_to_school(Variable): @@ -15,7 +18,7 @@ class goes_to_school(Variable): default_value = True entity = Person label = "The person goes to school (only relevant for children)" - definition_period = MONTH + definition_period = DateUnit.MONTH class WithBasicIncomeNeutralized(Reform): @@ -216,7 +219,7 @@ class new_variable(Variable): value_type = int label = "Nouvelle variable introduite par la réforme" entity = Household - definition_period = MONTH + definition_period = DateUnit.MONTH def formula(household, period): return household.empty_array() + 10 @@ -240,7 +243,7 @@ class new_dated_variable(Variable): value_type = int label = "Nouvelle variable introduite par la réforme" entity = Household - definition_period = MONTH + definition_period = DateUnit.MONTH def formula_2010_01_01(household, period): return household.empty_array() + 10 @@ -263,7 +266,7 @@ def apply(self): def test_update_variable(make_simulation, tax_benefit_system): class disposable_income(Variable): - definition_period = MONTH + definition_period = DateUnit.MONTH def formula_2018(household, period): return household.empty_array() + 10 @@ -294,7 +297,7 @@ def apply(self): def test_replace_variable(tax_benefit_system): class disposable_income(Variable): - definition_period = MONTH + definition_period = DateUnit.MONTH entity = Person label = "Disposable income" value_type = float @@ -355,9 +358,9 @@ class some_variable(Variable): value_type = int entity = Person label = "Variable with many attributes" - definition_period = MONTH - set_input = set_input_divide_by_period - calculate_output = calculate_output_add + definition_period = DateUnit.MONTH + set_input = holders.set_input_divide_by_period + calculate_output = simulations.calculate_output_add tax_benefit_system.add_variable(some_variable) diff --git a/tests/core/test_simulation_builder.py b/tests/core/test_simulation_builder.py index b6a558751d..7d451dc315 100644 --- a/tests/core/test_simulation_builder.py +++ b/tests/core/test_simulation_builder.py @@ -5,9 +5,10 @@ from openfisca_country_template import entities, situation_examples -from openfisca_core import periods, tools +from openfisca_core import tools from openfisca_core.errors import SituationParsingError from openfisca_core.indexed_enums import Enum +from openfisca_core.periods import DateUnit from openfisca_core.populations import Population from openfisca_core.simulations import Simulation, SimulationBuilder from openfisca_core.tools import test_runner @@ -18,7 +19,7 @@ def int_variable(persons): class intvar(Variable): - definition_period = periods.ETERNITY + definition_period = DateUnit.ETERNITY value_type = int entity = persons @@ -32,7 +33,7 @@ def __init__(self): def date_variable(persons): class datevar(Variable): - definition_period = periods.ETERNITY + definition_period = DateUnit.ETERNITY value_type = datetime.date entity = persons @@ -46,7 +47,7 @@ def __init__(self): def enum_variable(): class TestEnum(Variable): - definition_period = periods.ETERNITY + definition_period = DateUnit.ETERNITY value_type = Enum dtype = 'O' default_value = '0' diff --git a/tests/core/tools/test_runner/test_yaml_runner.py b/tests/core/tools/test_runner/test_yaml_runner.py index bd7aaccad7..24c4f5f15f 100644 --- a/tests/core/tools/test_runner/test_yaml_runner.py +++ b/tests/core/tools/test_runner/test_yaml_runner.py @@ -9,7 +9,7 @@ from openfisca_core.variables import Variable from openfisca_core.populations import Population from openfisca_core.entities import Entity -from openfisca_core.periods import ETERNITY +from openfisca_core.periods import DateUnit class TaxBenefitSystem: @@ -73,7 +73,7 @@ def __init__(self, test): class TestVariable(Variable): - definition_period = ETERNITY + definition_period = DateUnit.ETERNITY value_type = float def __init__(self): diff --git a/tests/core/variables/test_annualize.py b/tests/core/variables/test_annualize.py index 62b0a79b14..1529676574 100644 --- a/tests/core/variables/test_annualize.py +++ b/tests/core/variables/test_annualize.py @@ -1,10 +1,11 @@ import numpy as np from pytest import fixture -from openfisca_core import periods -from openfisca_core.model_api import * # noqa analysis:ignore from openfisca_country_template.entities import Person -from openfisca_core.variables import get_annualized_variable + +from openfisca_core import periods +from openfisca_core.periods import DateUnit +from openfisca_core.variables import Variable, get_annualized_variable @fixture @@ -15,7 +16,7 @@ def monthly_variable(): class monthly_variable(Variable): value_type = int entity = Person - definition_period = MONTH + definition_period = DateUnit.MONTH def formula(person, period, parameters): variable.calculation_count += 1 @@ -47,7 +48,7 @@ def test_without_annualize(monthly_variable): yearly_sum = sum( person('monthly_variable', month) - for month in period.get_subperiods(MONTH) + for month in period.get_subperiods(DateUnit.MONTH) ) assert monthly_variable.calculation_count == 11 @@ -62,7 +63,7 @@ def test_with_annualize(monthly_variable): yearly_sum = sum( person('monthly_variable', month) - for month in period.get_subperiods(MONTH) + for month in period.get_subperiods(DateUnit.MONTH) ) assert monthly_variable.calculation_count == 0 @@ -77,7 +78,7 @@ def test_with_partial_annualize(monthly_variable): yearly_sum = sum( person('monthly_variable', month) - for month in period.get_subperiods(MONTH) + for month in period.get_subperiods(DateUnit.MONTH) ) assert monthly_variable.calculation_count == 11 diff --git a/tests/core/variables/test_variables.py b/tests/core/variables/test_variables.py index 876145bde1..a8128b970b 100644 --- a/tests/core/variables/test_variables.py +++ b/tests/core/variables/test_variables.py @@ -2,16 +2,17 @@ import datetime -from openfisca_core.model_api import Variable -from openfisca_core.periods import MONTH, ETERNITY -from openfisca_core.simulation_builder import SimulationBuilder -from openfisca_core.tools import assert_near +from pytest import fixture, raises, mark import openfisca_country_template as country_template import openfisca_country_template.situation_examples from openfisca_country_template.entities import Person -from pytest import fixture, raises, mark +from openfisca_core.periods import DateUnit +from openfisca_core.simulation_builder import SimulationBuilder +from openfisca_core.tools import assert_near +from openfisca_core.variables import Variable + # Check which date is applied whether it comes from Variable attribute (end) # or formula(s) dates. @@ -58,7 +59,7 @@ def get_message(error): class variable__no_date(Variable): value_type = int entity = Person - definition_period = MONTH + definition_period = DateUnit.MONTH label = "Variable without date." @@ -78,7 +79,7 @@ def test_variable__no_date(): class variable__strange_end_attribute(Variable): value_type = int entity = Person - definition_period = MONTH + definition_period = DateUnit.MONTH label = "Variable with dubious end attribute, no formula." end = '1989-00-00' @@ -100,7 +101,7 @@ def test_variable__strange_end_attribute(): class variable__end_attribute(Variable): value_type = int entity = Person - definition_period = MONTH + definition_period = DateUnit.MONTH label = "Variable with end attribute, no formula." end = '1989-12-31' @@ -127,7 +128,7 @@ def test_variable__end_attribute_set_input(simulation): class end_attribute__one_simple_formula(Variable): value_type = int entity = Person - definition_period = MONTH + definition_period = DateUnit.MONTH label = "Variable with end attribute, one formula without date." end = '1989-12-31' @@ -170,7 +171,7 @@ def test_dates__end_attribute__one_simple_formula(): class no_end_attribute__one_formula__strange_name(Variable): value_type = int entity = Person - definition_period = MONTH + definition_period = DateUnit.MONTH label = "Variable without end attribute, one stangely named formula." def formula_2015_toto(individu, period): @@ -187,7 +188,7 @@ def test_add__no_end_attribute__one_formula__strange_name(): class no_end_attribute__one_formula__start(Variable): value_type = int entity = Person - definition_period = MONTH + definition_period = DateUnit.MONTH label = "Variable without end attribute, one dated formula." def formula_2000_01_01(individu, period): @@ -219,7 +220,7 @@ def test_dates__no_end_attribute__one_formula__start(): class no_end_attribute__one_formula__eternity(Variable): value_type = int entity = Person - definition_period = ETERNITY # For this entity, this variable shouldn't evolve through time + definition_period = DateUnit.ETERNITY # For this entity, this variable shouldn't evolve through time label = "Variable without end attribute, one dated formula." def formula_2000_01_01(individu, period): @@ -255,7 +256,7 @@ def test_call__no_end_attribute__one_formula__eternity_after(simulation): class no_end_attribute__formulas__start_formats(Variable): value_type = int entity = Person - definition_period = MONTH + definition_period = DateUnit.MONTH label = "Variable without end attribute, multiple dated formulas." def formula_2000(individu, period): @@ -307,7 +308,7 @@ def test_call__no_end_attribute__formulas__start_formats(simulation): class no_attribute__formulas__different_names__dates_overlap(Variable): value_type = int entity = Person - definition_period = MONTH + definition_period = DateUnit.MONTH label = "Variable, no end attribute, multiple dated formulas with different names but same dates." def formula_2000(individu, period): @@ -327,7 +328,7 @@ def test_add__no_attribute__formulas__different_names__dates_overlap(): class no_attribute__formulas__different_names__no_overlap(Variable): value_type = int entity = Person - definition_period = MONTH + definition_period = DateUnit.MONTH label = "Variable, no end attribute, multiple dated formulas with different names and no date overlap." def formula_2000_01_01(individu, period): @@ -356,7 +357,7 @@ def test_call__no_attribute__formulas__different_names__no_overlap(simulation): class end_attribute__one_formula__start(Variable): value_type = int entity = Person - definition_period = MONTH + definition_period = DateUnit.MONTH label = "Variable with end attribute, one dated formula." end = '2001-12-31' @@ -383,7 +384,7 @@ def test_call__end_attribute__one_formula__start(simulation): class stop_attribute_before__one_formula__start(Variable): value_type = int entity = Person - definition_period = MONTH + definition_period = DateUnit.MONTH label = "Variable with stop attribute only coming before formula start." end = '1990-01-01' @@ -400,7 +401,7 @@ def test_add__stop_attribute_before__one_formula__start(): class end_attribute_restrictive__one_formula(Variable): value_type = int entity = Person - definition_period = MONTH + definition_period = DateUnit.MONTH label = "Variable with end attribute, one dated formula and dates intervals overlap." end = '2001-01-01' @@ -427,7 +428,7 @@ def test_call__end_attribute_restrictive__one_formula(simulation): class end_attribute__formulas__different_names(Variable): value_type = int entity = Person - definition_period = MONTH + definition_period = DateUnit.MONTH label = "Variable with end attribute, multiple dated formulas with different names." end = '2010-12-31' @@ -468,7 +469,7 @@ def test_unexpected_attr(): class variable_with_strange_attr(Variable): value_type = int entity = Person - definition_period = MONTH + definition_period = DateUnit.MONTH unexpected = '???' with raises(ValueError): diff --git a/tests/fixtures/appclient.py b/tests/fixtures/appclient.py index a140e0f938..1810488d5f 100644 --- a/tests/fixtures/appclient.py +++ b/tests/fixtures/appclient.py @@ -20,7 +20,7 @@ def test_client(tax_benefit_system): class new_variable(Variable): value_type = float entity = entities.Person - definition_period = periods.MONTH + definition_period = DateUnit.MONTH label = "New variable" reference = "https://law.gov.example/new_variable" # Always use the most official source diff --git a/tests/fixtures/variables.py b/tests/fixtures/variables.py index cd0d9b70ce..aab7cda58d 100644 --- a/tests/fixtures/variables.py +++ b/tests/fixtures/variables.py @@ -1,9 +1,9 @@ -from openfisca_core import periods +from openfisca_core.periods import DateUnit from openfisca_core.variables import Variable class TestVariable(Variable): - definition_period = periods.ETERNITY + definition_period = DateUnit.ETERNITY value_type = float def __init__(self, entity): From 4fbb04c9d5c1815f3b9d7e21a3dc1896e3375664 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Fri, 29 Jul 2022 04:19:09 +0200 Subject: [PATCH 24/70] Version & CHANGELOG bump --- CHANGELOG.md | 8 ++++++++ setup.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e995522107..c89b415883 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 35.9.0 [#1139](https://github.com/openfisca/openfisca-core/pull/1139) + +#### New features + +- Introduce `DateUnit` to `periods` + - Allows for transparent encapsulation of the atomic units of `periods` (`year`, `month`, ...). + - This helps with testing, refactoring and exrtension of `periods` (`weeks` for example). + ### 35.8.6 [#1138](https://github.com/openfisca/openfisca-core/pull/1138) #### Technical changes diff --git a/setup.py b/setup.py index 5821d259fa..edbc4b22ed 100644 --- a/setup.py +++ b/setup.py @@ -48,7 +48,7 @@ setup( name = 'OpenFisca-Core', - version = '35.8.6', + version = '35.9.0', author = 'OpenFisca Team', author_email = 'contact@openfisca.org', classifiers = [ From 9872a39e4eeb44f20f074330b8cbc0fc6c13c029 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Sun, 31 Jul 2022 14:50:28 +0200 Subject: [PATCH 25/70] Fix flake8/pycodestyle dependency error --- setup.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index 2543121ac8..38837bacb1 100644 --- a/setup.py +++ b/setup.py @@ -38,18 +38,18 @@ ] api_requirements = [ - 'markupsafe == 2.0.1', # While flask revision < 2 - 'flask == 1.1.4', + 'markupsafe >= 2.0.1, < 2.1.0', + 'flask >= 1.1.4, < 2.0.0', 'flask-cors == 3.0.10', 'gunicorn >= 20.0.0, < 21.0.0', - 'werkzeug >= 1.0.0, < 2.0.0', + 'werkzeug >= 1.0.1, < 2.0.0', ] dev_requirements = [ 'autopep8 >= 1.4.0, < 1.6.0', 'coverage == 6.0.2', 'darglint == 1.8.0', - 'flake8 >= 4.0.0, < 4.1.0', + 'flake8 >= 3.9.0, < 4.0.0', 'flake8-bugbear >= 19.3.0, < 20.0.0', 'flake8-docstrings == 1.6.0', 'flake8-print >= 3.1.0, < 4.0.0', From 35e02c5ca877be1617ac567651a5ee96af170986 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Thu, 28 Jul 2022 18:36:11 +0200 Subject: [PATCH 26/70] Add periods to doctests path --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 467e3ede59..31815f0da2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -14,7 +14,7 @@ extend-ignore = D hang-closing = true ignore = E128,E251,F403,F405,E501,RST301,W503,W504 in-place = true -include-in-doctest = openfisca_core/commons openfisca_core/holders openfisca_core/types +include-in-doctest = openfisca_core/commons openfisca_core/holders openfisca_core/periods openfisca_core/types rst-directives = attribute, deprecated, seealso, versionadded, versionchanged rst-roles = any, attr, class, exc, func, meth, mod, obj strictness = short From 8ba7fe1ca7bea87683d0c35d4aaeedc888221baf Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Thu, 28 Jul 2022 18:37:47 +0200 Subject: [PATCH 27/70] Ignore mypy periods' tests errors --- setup.cfg | 3 +++ 1 file changed, 3 insertions(+) diff --git a/setup.cfg b/setup.cfg index 31815f0da2..2144a48610 100644 --- a/setup.cfg +++ b/setup.cfg @@ -54,5 +54,8 @@ ignore_errors = True [mypy-openfisca_core.holders.tests.*] ignore_errors = True +[mypy-openfisca_core.periods.tests.*] +ignore_errors = True + [mypy-openfisca_core.scripts.*] ignore_errors = True From 496951f7c09dd9f0cd6c6412d9562bb3eb630200 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Thu, 28 Jul 2022 19:53:11 +0200 Subject: [PATCH 28/70] Fix instants' docstrings --- openfisca_core/periods/instant_.py | 242 ++++++++++++----------------- 1 file changed, 102 insertions(+), 140 deletions(-) diff --git a/openfisca_core/periods/instant_.py b/openfisca_core/periods/instant_.py index ad559e5e53..48c3a1d995 100644 --- a/openfisca_core/periods/instant_.py +++ b/openfisca_core/periods/instant_.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import calendar import datetime @@ -5,32 +7,83 @@ class Instant(tuple): + """An instant in time (year, month, day). + + An :class:`.Instant` represents the most atomic and indivisible + legislation's time unit. + + Current implementation considers this unit to be a day, so + :obj:`instants <.Instant>` can be thought of as "day dates". + + Args: + tuple(tuple(int, int, int)): + The ``year``, ``month``, and ``day``, accordingly. + + Examples: + >>> instant = Instant((2021, 9, 13)) + + >>> repr(Instant) + "" + + >>> repr(instant) + 'Instant((2021, 9, 13))' + + >>> str(instant) + '2021-09-13' + + >>> dict([(instant, (2021, 9, 13))]) + {Instant((2021, 9, 13)): (2021, 9, 13)} + + >>> list(instant) + [2021, 9, 13] + + >>> instant[0] + 2021 + + >>> instant[0] in instant + True + + >>> len(instant) + 3 + + >>> instant == (2021, 9, 13) + True + + >>> instant != (2021, 9, 13) + False + + >>> instant > (2020, 9, 13) + True + + >>> instant < (2020, 9, 13) + False + + >>> instant >= (2020, 9, 13) + True + + >>> instant <= (2020, 9, 13) + False + + >>> instant.year + 2021 + + >>> instant.month + 9 + + >>> instant.day + 13 + + >>> instant.date + datetime.date(2021, 9, 13) + + >>> year, month, day = instant + + """ def __repr__(self): - """ - Transform instant to to its Python representation as a string. - - >>> repr(instant(2014)) - 'Instant((2014, 1, 1))' - >>> repr(instant('2014-2')) - 'Instant((2014, 2, 1))' - >>> repr(instant('2014-2-3')) - 'Instant((2014, 2, 3))' - """ return '{}({})'.format(self.__class__.__name__, super(Instant, self).__repr__()) def __str__(self): - """ - Transform instant to a string. - - >>> str(instant(2014)) - '2014-01-01' - >>> str(instant('2014-2')) - '2014-02-01' - >>> str(instant('2014-2-3')) - '2014-02-03' - - """ instant_str = config.str_by_instant_cache.get(self) if instant_str is None: config.str_by_instant_cache[self] = instant_str = self.date.isoformat() @@ -38,16 +91,6 @@ def __str__(self): @property def date(self): - """ - Convert instant to a date. - - >>> instant(2014).date - datetime.date(2014, 1, 1) - >>> instant('2014-2').date - datetime.date(2014, 2, 1) - >>> instant('2014-2-3').date - datetime.date(2014, 2, 3) - """ instant_date = config.date_by_instant_cache.get(self) if instant_date is None: config.date_by_instant_cache[self] = instant_date = datetime.date(*self) @@ -55,113 +98,42 @@ def date(self): @property def day(self): - """ - Extract day from instant. - - >>> instant(2014).day - 1 - >>> instant('2014-2').day - 1 - >>> instant('2014-2-3').day - 3 - """ return self[2] @property def month(self): - """ - Extract month from instant. - - >>> instant(2014).month - 1 - >>> instant('2014-2').month - 2 - >>> instant('2014-2-3').month - 2 - """ return self[1] def offset(self, offset, unit): + """Increments/decrements the given instant with offset units. + + Args: + offset: How much of ``unit`` to offset. + unit: What to offset + + Returns: + :obj:`.Instant`: A new :obj:`.Instant` in time. + + Raises: + :exc:`AssertionError`: When ``unit`` is not a date unit. + :exc:`AssertionError`: When ``offset`` is not either ``first-of``, + ``last-of``, or any :obj:`int`. + + Examples: + >>> Instant((2020, 12, 31)).offset("first-of", "month") + Instant((2020, 12, 1)) + + >>> Instant((2020, 1, 1)).offset("last-of", "year") + Instant((2020, 12, 31)) + + >>> Instant((2020, 1, 1)).offset(1, "year") + Instant((2021, 1, 1)) + + >>> Instant((2020, 1, 1)).offset(-3, "day") + Instant((2019, 12, 29)) + """ - Increment (or decrement) the given instant with offset units. - - >>> instant(2014).offset(1, 'day') - Instant((2014, 1, 2)) - >>> instant(2014).offset(1, 'month') - Instant((2014, 2, 1)) - >>> instant(2014).offset(1, 'year') - Instant((2015, 1, 1)) - - >>> instant('2014-1-31').offset(1, 'day') - Instant((2014, 2, 1)) - >>> instant('2014-1-31').offset(1, 'month') - Instant((2014, 2, 28)) - >>> instant('2014-1-31').offset(1, 'year') - Instant((2015, 1, 31)) - - >>> instant('2011-2-28').offset(1, 'day') - Instant((2011, 3, 1)) - >>> instant('2011-2-28').offset(1, 'month') - Instant((2011, 3, 28)) - >>> instant('2012-2-29').offset(1, 'year') - Instant((2013, 2, 28)) - - >>> instant(2014).offset(-1, 'day') - Instant((2013, 12, 31)) - >>> instant(2014).offset(-1, 'month') - Instant((2013, 12, 1)) - >>> instant(2014).offset(-1, 'year') - Instant((2013, 1, 1)) - - >>> instant('2011-3-1').offset(-1, 'day') - Instant((2011, 2, 28)) - >>> instant('2011-3-31').offset(-1, 'month') - Instant((2011, 2, 28)) - >>> instant('2012-2-29').offset(-1, 'year') - Instant((2011, 2, 28)) - - >>> instant('2014-1-30').offset(3, 'day') - Instant((2014, 2, 2)) - >>> instant('2014-10-2').offset(3, 'month') - Instant((2015, 1, 2)) - >>> instant('2014-1-1').offset(3, 'year') - Instant((2017, 1, 1)) - - >>> instant(2014).offset(-3, 'day') - Instant((2013, 12, 29)) - >>> instant(2014).offset(-3, 'month') - Instant((2013, 10, 1)) - >>> instant(2014).offset(-3, 'year') - Instant((2011, 1, 1)) - - >>> instant(2014).offset('first-of', 'month') - Instant((2014, 1, 1)) - >>> instant('2014-2').offset('first-of', 'month') - Instant((2014, 2, 1)) - >>> instant('2014-2-3').offset('first-of', 'month') - Instant((2014, 2, 1)) - - >>> instant(2014).offset('first-of', 'year') - Instant((2014, 1, 1)) - >>> instant('2014-2').offset('first-of', 'year') - Instant((2014, 1, 1)) - >>> instant('2014-2-3').offset('first-of', 'year') - Instant((2014, 1, 1)) - - >>> instant(2014).offset('last-of', 'month') - Instant((2014, 1, 31)) - >>> instant('2014-2').offset('last-of', 'month') - Instant((2014, 2, 28)) - >>> instant('2012-2-3').offset('last-of', 'month') - Instant((2012, 2, 29)) - - >>> instant(2014).offset('last-of', 'year') - Instant((2014, 12, 31)) - >>> instant('2014-2').offset('last-of', 'year') - Instant((2014, 12, 31)) - >>> instant('2014-2-3').offset('last-of', 'year') - Instant((2014, 12, 31)) - """ + year, month, day = self assert unit in (config.DAY, config.MONTH, config.YEAR), 'Invalid unit: {} of type {}'.format(unit, type(unit)) if offset == 'first-of': @@ -220,14 +192,4 @@ def offset(self, offset, unit): @property def year(self): - """ - Extract year from instant. - - >>> instant(2014).year - 2014 - >>> instant('2014-2').year - 2014 - >>> instant('2014-2-3').year - 2014 - """ return self[0] From fbafc60780fa37ed750289c5210de4c685613d33 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Thu, 28 Jul 2022 20:56:36 +0200 Subject: [PATCH 29/70] Fix periods' doctests --- openfisca_core/periods/period_.py | 614 ++++++++++++++++++------------ setup.py | 2 +- 2 files changed, 365 insertions(+), 251 deletions(-) diff --git a/openfisca_core/periods/period_.py b/openfisca_core/periods/period_.py index 7de0459bdf..9f2d9c29f9 100644 --- a/openfisca_core/periods/period_.py +++ b/openfisca_core/periods/period_.py @@ -7,54 +7,158 @@ class Period(tuple): - """ - Toolbox to handle date intervals. + """Toolbox to handle date intervals. + + A :class:`.Period` is a triple (``unit``, ``start``, ``size``). + + Attributes: + unit (:obj:`.DateUnit`): + Either an :meth:`~DateUnit.isoformat` unit (``day``, ``month``, + ``year``), an :meth:`~DateUnit.isocalendar` one (``week_day``, + ``week``, ``year``), or :obj:`~DateUnit.ETERNITY`. + start (:obj:`.Instant`): + The "instant" the :obj:`.Period` starts at. + size (:obj:`int`): + The amount of ``unit``, starting at ``start``, at least ``1``. + + Args: + fragments (tuple(.DateUnit, .Instant, int)): + The ``unit``, ``start``, and ``size``, accordingly. + + Examples: + >>> instant = Instant((2021, 9, 1)) + >>> period = Period(("year", instant, 3)) + + >>> repr(Period) + "" + + >>> repr(period) + "Period(('year', Instant((2021, 9, 1)), 3))" + + >>> str(period) + 'year:2021-09:3' + + >>> dict([period, instant]) + Traceback (most recent call last): + ValueError: dictionary update sequence element #0 has length 3; 2 is required + + >>> list(period) + ['year', Instant((2021, 9, 1)), 3] + + >>> period[0] + 'year' + + >>> period[0] in period + True + + >>> len(period) + 3 + + >>> period == Period(("year", instant, 3)) + True + + >>> period != Period(("year", instant, 3)) + False + + >>> period > Period(("year", instant, 3)) + False + + >>> period < Period(("year", instant, 3)) + False + + >>> period >= Period(("year", instant, 3)) + True - A period is a triple (unit, start, size), where unit is either "month" or "year", where start format is a - (year, month, day) triple, and where size is an integer > 1. + >>> period <= Period(("year", instant, 3)) + True + >>> period.date + Traceback (most recent call last): + AssertionError: "date" is undefined for a period of size > 1 + + >>> Period(("year", instant, 1)).date + datetime.date(2021, 9, 1) + + >>> period.days + 1096 + + >>> period.size + 3 + + >>> period.size_in_months + 36 + + >>> period.size_in_days + 1096 + + >>> period.start + Instant((2021, 9, 1)) + + >>> period.stop + Instant((2024, 8, 31)) + + >>> period.unit + 'year' + + >>> period.last_3_months + Period(('month', Instant((2021, 6, 1)), 3)) + + >>> period.last_month + Period(('month', Instant((2021, 8, 1)), 1)) + + >>> period.last_year + Period(('year', Instant((2020, 1, 1)), 1)) + + >>> period.n_2 + Period(('year', Instant((2019, 1, 1)), 1)) + + >>> period.this_year + Period(('year', Instant((2021, 1, 1)), 1)) + + >>> period.first_month + Period(('month', Instant((2021, 9, 1)), 1)) + + >>> period.first_day + Period(('day', Instant((2021, 9, 1)), 1)) Since a period is a triple it can be used as a dictionary key. + """ def __repr__(self): - """ - Transform period to to its Python representation as a string. - - >>> repr(period('year', 2014)) - "Period(('year', Instant((2014, 1, 1)), 1))" - >>> repr(period('month', '2014-2')) - "Period(('month', Instant((2014, 2, 1)), 1))" - >>> repr(period('day', '2014-2-3')) - "Period(('day', Instant((2014, 2, 3)), 1))" - """ return '{}({})'.format(self.__class__.__name__, super(Period, self).__repr__()) def __str__(self): - """ - Transform period to a string. - - >>> str(period(YEAR, 2014)) - '2014' - - >>> str(period(YEAR, '2014-2')) - 'year:2014-02' - >>> str(period(MONTH, '2014-2')) - '2014-02' - - >>> str(period(YEAR, 2012, size = 2)) - 'year:2012:2' - >>> str(period(MONTH, 2012, size = 2)) - 'month:2012-01:2' - >>> str(period(MONTH, 2012, size = 12)) - '2012' - - >>> str(period(YEAR, '2012-3', size = 2)) - 'year:2012-03:2' - >>> str(period(MONTH, '2012-3', size = 2)) - 'month:2012-03:2' - >>> str(period(MONTH, '2012-3', size = 12)) - 'year:2012-03' + """Transform period to a string. + + Examples: + >>> str(Period(("year", Instant((2021, 1, 1)), 1))) + '2021' + + >>> str(Period(("year", Instant((2021, 2, 1)), 1))) + 'year:2021-02' + + >>> str(Period(("month", Instant((2021, 2, 1)), 1))) + '2021-02' + + >>> str(Period(("year", Instant((2021, 1, 1)), 2))) + 'year:2021:2' + + >>> str(Period(("month", Instant((2021, 1, 1)), 2))) + 'month:2021-01:2' + + >>> str(Period(("month", Instant((2021, 1, 1)), 12))) + '2021' + + >>> str(Period(("year", Instant((2021, 3, 1)), 2))) + 'year:2021-03:2' + + >>> str(Period(("month", Instant((2021, 3, 1)), 2))) + 'month:2021-03:2' + + >>> str(Period(("month", Instant((2021, 3, 1)), 12))) + 'year:2021-03' + """ unit, start_instant, size = self @@ -93,30 +197,8 @@ def date(self): @property def days(self): - """ - Count the number of days in period. - - >>> period('day', 2014).days - 365 - >>> period('month', 2014).days - 365 - >>> period('year', 2014).days - 365 - - >>> period('day', '2014-2').days - 28 - >>> period('month', '2014-2').days - 28 - >>> period('year', '2014-2').days - 365 - - >>> period('day', '2014-2-3').days - 1 - >>> period('month', '2014-2-3').days - 28 - >>> period('year', '2014-2-3').days - 365 - """ + """Count the number of days in period.""" + return (self.stop.date - self.start.date).days + 1 def intersection(self, start, stop): @@ -160,17 +242,19 @@ def intersection(self, start, stop): )) def get_subperiods(self, unit): - """ - Return the list of all the periods of unit ``unit`` contained in self. + """Return the list of all the periods of unit ``unit`` contained in self. Examples: + >>> period = Period(("year", Instant((2021, 1, 1)), 1)) + >>> period.get_subperiods("month") + [Period(('month', Instant((2021, 1, 1)), 1)), ...2021, 12, 1)), 1))] - >>> period('2017').get_subperiods(MONTH) - >>> [period('2017-01'), period('2017-02'), ... period('2017-12')] + >>> period = Period(("year", Instant((2021, 1, 1)), 2)) + >>> period.get_subperiods("year") + [Period(('year', Instant((2021, 1, 1)), 1)), ...((2022, 1, 1)), 1))] - >>> period('year:2014:2').get_subperiods(YEAR) - >>> [period('2014'), period('2015')] """ + if helpers.unit_weight(self.unit) < helpers.unit_weight(unit): raise ValueError('Cannot subdivide {0} into {1}'.format(self.unit, unit)) @@ -184,168 +268,200 @@ def get_subperiods(self, unit): return [self.first_day.offset(i, config.DAY) for i in range(self.size_in_days)] def offset(self, offset, unit = None): + """Increment (or decrement) the given period with offset units. + + Examples: + >>> Period(("day", Instant((2021, 1, 1)), 365)).offset(1) + Period(('day', Instant((2021, 1, 2)), 365)) + + >>> Period(("day", Instant((2021, 1, 1)), 365)).offset(1, "day") + Period(('day', Instant((2021, 1, 2)), 365)) + + >>> Period(("day", Instant((2021, 1, 1)), 365)).offset(1, "month") + Period(('day', Instant((2021, 2, 1)), 365)) + + >>> Period(("day", Instant((2021, 1, 1)), 365)).offset(1, "year") + Period(('day', Instant((2022, 1, 1)), 365)) + + >>> Period(("month", Instant((2021, 1, 1)), 12)).offset(1) + Period(('month', Instant((2021, 2, 1)), 12)) + + >>> Period(("month", Instant((2021, 1, 1)), 12)).offset(1, "day") + Period(('month', Instant((2021, 1, 2)), 12)) + + >>> Period(("month", Instant((2021, 1, 1)), 12)).offset(1, "month") + Period(('month', Instant((2021, 2, 1)), 12)) + + >>> Period(("month", Instant((2021, 1, 1)), 12)).offset(1, "year") + Period(('month', Instant((2022, 1, 1)), 12)) + + >>> Period(("year", Instant((2021, 1, 1)), 1)).offset(1) + Period(('year', Instant((2022, 1, 1)), 1)) + + >>> Period(("year", Instant((2021, 1, 1)), 1)).offset(1, "day") + Period(('year', Instant((2021, 1, 2)), 1)) + + >>> Period(("year", Instant((2021, 1, 1)), 1)).offset(1, "month") + Period(('year', Instant((2021, 2, 1)), 1)) + + >>> Period(("year", Instant((2021, 1, 1)), 1)).offset(1, "year") + Period(('year', Instant((2022, 1, 1)), 1)) + + >>> Period(("day", Instant((2011, 2, 28)), 1)).offset(1) + Period(('day', Instant((2011, 3, 1)), 1)) + + >>> Period(("month", Instant((2011, 2, 28)), 1)).offset(1) + Period(('month', Instant((2011, 3, 28)), 1)) + + >>> Period(("year", Instant((2011, 2, 28)), 1)).offset(1) + Period(('year', Instant((2012, 2, 28)), 1)) + + >>> Period(("day", Instant((2011, 3, 1)), 1)).offset(-1) + Period(('day', Instant((2011, 2, 28)), 1)) + + >>> Period(("month", Instant((2011, 3, 1)), 1)).offset(-1) + Period(('month', Instant((2011, 2, 1)), 1)) + + >>> Period(("year", Instant((2011, 3, 1)), 1)).offset(-1) + Period(('year', Instant((2010, 3, 1)), 1)) + + >>> Period(("day", Instant((2014, 1, 30)), 1)).offset(3) + Period(('day', Instant((2014, 2, 2)), 1)) + + >>> Period(("month", Instant((2014, 1, 30)), 1)).offset(3) + Period(('month', Instant((2014, 4, 30)), 1)) + + >>> Period(("year", Instant((2014, 1, 30)), 1)).offset(3) + Period(('year', Instant((2017, 1, 30)), 1)) + + >>> Period(("day", Instant((2021, 1, 1)), 365)).offset(-3) + Period(('day', Instant((2020, 12, 29)), 365)) + + >>> Period(("month", Instant((2021, 1, 1)), 12)).offset(-3) + Period(('month', Instant((2020, 10, 1)), 12)) + + >>> Period(("year", Instant((2014, 1, 1)), 1)).offset(-3) + Period(('year', Instant((2011, 1, 1)), 1)) + + >>> Period(("day", Instant((2014, 2, 3)), 1)).offset("first-of", "month") + Period(('day', Instant((2014, 2, 1)), 1)) + + >>> Period(("day", Instant((2014, 2, 3)), 1)).offset("first-of", "year") + Period(('day', Instant((2014, 1, 1)), 1)) + + >>> Period(("day", Instant((2014, 2, 3)), 4)).offset("first-of", "month") + Period(('day', Instant((2014, 2, 1)), 4)) + + >>> Period(("day", Instant((2014, 2, 3)), 4)).offset("first-of", "year") + Period(('day', Instant((2014, 1, 1)), 4)) + + >>> Period(("month", Instant((2014, 2, 3)), 1)).offset("first-of") + Period(('month', Instant((2014, 2, 1)), 1)) + + >>> Period(("month", Instant((2014, 2, 3)), 1)).offset("first-of", "month") + Period(('month', Instant((2014, 2, 1)), 1)) + + >>> Period(("month", Instant((2014, 2, 3)), 1)).offset("first-of", "year") + Period(('month', Instant((2014, 1, 1)), 1)) + + >>> Period(("month", Instant((2014, 2, 3)), 4)).offset("first-of") + Period(('month', Instant((2014, 2, 1)), 4)) + + >>> Period(("month", Instant((2014, 2, 3)), 4)).offset("first-of", "month") + Period(('month', Instant((2014, 2, 1)), 4)) + + >>> Period(("month", Instant((2014, 2, 3)), 4)).offset("first-of", "year") + Period(('month', Instant((2014, 1, 1)), 4)) + + >>> Period(("year", Instant((2014, 1, 30)), 1)).offset("first-of") + Period(('year', Instant((2014, 1, 1)), 1)) + + >>> Period(("year", Instant((2014, 1, 30)), 1)).offset("first-of", "month") + Period(('year', Instant((2014, 1, 1)), 1)) + + >>> Period(("year", Instant((2014, 1, 30)), 1)).offset("first-of", "year") + Period(('year', Instant((2014, 1, 1)), 1)) + + >>> Period(("year", Instant((2014, 2, 3)), 1)).offset("first-of") + Period(('year', Instant((2014, 1, 1)), 1)) + + >>> Period(("year", Instant((2014, 2, 3)), 1)).offset("first-of", "month") + Period(('year', Instant((2014, 2, 1)), 1)) + + >>> Period(("year", Instant((2014, 2, 3)), 1)).offset("first-of", "year") + Period(('year', Instant((2014, 1, 1)), 1)) + + >>> Period(("day", Instant((2014, 2, 3)), 1)).offset("last-of", "month") + Period(('day', Instant((2014, 2, 28)), 1)) + + >>> Period(("day", Instant((2014, 2, 3)), 1)).offset("last-of", "year") + Period(('day', Instant((2014, 12, 31)), 1)) + + >>> Period(("day", Instant((2014, 2, 3)), 4)).offset("last-of", "month") + Period(('day', Instant((2014, 2, 28)), 4)) + + >>> Period(("day", Instant((2014, 2, 3)), 4)).offset("last-of", "year") + Period(('day', Instant((2014, 12, 31)), 4)) + + >>> Period(("month", Instant((2014, 2, 3)), 1)).offset("last-of") + Period(('month', Instant((2014, 2, 28)), 1)) + + >>> Period(("month", Instant((2014, 2, 3)), 1)).offset("last-of", "month") + Period(('month', Instant((2014, 2, 28)), 1)) + + >>> Period(("month", Instant((2014, 2, 3)), 1)).offset("last-of", "year") + Period(('month', Instant((2014, 12, 31)), 1)) + + >>> Period(("month", Instant((2014, 2, 3)), 4)).offset("last-of") + Period(('month', Instant((2014, 2, 28)), 4)) + + >>> Period(("month", Instant((2014, 2, 3)), 4)).offset("last-of", "month") + Period(('month', Instant((2014, 2, 28)), 4)) + + >>> Period(("month", Instant((2014, 2, 3)), 4)).offset("last-of", "year") + Period(('month', Instant((2014, 12, 31)), 4)) + + >>> Period(("year", Instant((2014, 2, 3)), 1)).offset("last-of") + Period(('year', Instant((2014, 12, 31)), 1)) + + >>> Period(("year", Instant((2014, 1, 1)), 1)).offset("last-of", "month") + Period(('year', Instant((2014, 1, 31)), 1)) + + >>> Period(("year", Instant((2014, 2, 3)), 1)).offset("last-of", "year") + Period(('year', Instant((2014, 12, 31)), 1)) + + >>> Period(("year", Instant((2014, 2, 3)), 1)).offset("last-of") + Period(('year', Instant((2014, 12, 31)), 1)) + + >>> Period(("year", Instant((2014, 2, 3)), 1)).offset("last-of", "month") + Period(('year', Instant((2014, 2, 28)), 1)) + + >>> Period(("year", Instant((2014, 2, 3)), 1)).offset("last-of", "year") + Period(('year', Instant((2014, 12, 31)), 1)) + """ - Increment (or decrement) the given period with offset units. - - >>> period('day', 2014).offset(1) - Period(('day', Instant((2014, 1, 2)), 365)) - >>> period('day', 2014).offset(1, 'day') - Period(('day', Instant((2014, 1, 2)), 365)) - >>> period('day', 2014).offset(1, 'month') - Period(('day', Instant((2014, 2, 1)), 365)) - >>> period('day', 2014).offset(1, 'year') - Period(('day', Instant((2015, 1, 1)), 365)) - - >>> period('month', 2014).offset(1) - Period(('month', Instant((2014, 2, 1)), 12)) - >>> period('month', 2014).offset(1, 'day') - Period(('month', Instant((2014, 1, 2)), 12)) - >>> period('month', 2014).offset(1, 'month') - Period(('month', Instant((2014, 2, 1)), 12)) - >>> period('month', 2014).offset(1, 'year') - Period(('month', Instant((2015, 1, 1)), 12)) - - >>> period('year', 2014).offset(1) - Period(('year', Instant((2015, 1, 1)), 1)) - >>> period('year', 2014).offset(1, 'day') - Period(('year', Instant((2014, 1, 2)), 1)) - >>> period('year', 2014).offset(1, 'month') - Period(('year', Instant((2014, 2, 1)), 1)) - >>> period('year', 2014).offset(1, 'year') - Period(('year', Instant((2015, 1, 1)), 1)) - - >>> period('day', '2011-2-28').offset(1) - Period(('day', Instant((2011, 3, 1)), 1)) - >>> period('month', '2011-2-28').offset(1) - Period(('month', Instant((2011, 3, 28)), 1)) - >>> period('year', '2011-2-28').offset(1) - Period(('year', Instant((2012, 2, 28)), 1)) - - >>> period('day', '2011-3-1').offset(-1) - Period(('day', Instant((2011, 2, 28)), 1)) - >>> period('month', '2011-3-1').offset(-1) - Period(('month', Instant((2011, 2, 1)), 1)) - >>> period('year', '2011-3-1').offset(-1) - Period(('year', Instant((2010, 3, 1)), 1)) - - >>> period('day', '2014-1-30').offset(3) - Period(('day', Instant((2014, 2, 2)), 1)) - >>> period('month', '2014-1-30').offset(3) - Period(('month', Instant((2014, 4, 30)), 1)) - >>> period('year', '2014-1-30').offset(3) - Period(('year', Instant((2017, 1, 30)), 1)) - - >>> period('day', 2014).offset(-3) - Period(('day', Instant((2013, 12, 29)), 365)) - >>> period('month', 2014).offset(-3) - Period(('month', Instant((2013, 10, 1)), 12)) - >>> period('year', 2014).offset(-3) - Period(('year', Instant((2011, 1, 1)), 1)) - - >>> period('day', '2014-2-3').offset('first-of', 'month') - Period(('day', Instant((2014, 2, 1)), 1)) - >>> period('day', '2014-2-3').offset('first-of', 'year') - Period(('day', Instant((2014, 1, 1)), 1)) - - >>> period('day', '2014-2-3', 4).offset('first-of', 'month') - Period(('day', Instant((2014, 2, 1)), 4)) - >>> period('day', '2014-2-3', 4).offset('first-of', 'year') - Period(('day', Instant((2014, 1, 1)), 4)) - - >>> period('month', '2014-2-3').offset('first-of') - Period(('month', Instant((2014, 2, 1)), 1)) - >>> period('month', '2014-2-3').offset('first-of', 'month') - Period(('month', Instant((2014, 2, 1)), 1)) - >>> period('month', '2014-2-3').offset('first-of', 'year') - Period(('month', Instant((2014, 1, 1)), 1)) - - >>> period('month', '2014-2-3', 4).offset('first-of') - Period(('month', Instant((2014, 2, 1)), 4)) - >>> period('month', '2014-2-3', 4).offset('first-of', 'month') - Period(('month', Instant((2014, 2, 1)), 4)) - >>> period('month', '2014-2-3', 4).offset('first-of', 'year') - Period(('month', Instant((2014, 1, 1)), 4)) - - >>> period('year', 2014).offset('first-of') - Period(('year', Instant((2014, 1, 1)), 1)) - >>> period('year', 2014).offset('first-of', 'month') - Period(('year', Instant((2014, 1, 1)), 1)) - >>> period('year', 2014).offset('first-of', 'year') - Period(('year', Instant((2014, 1, 1)), 1)) - - >>> period('year', '2014-2-3').offset('first-of') - Period(('year', Instant((2014, 1, 1)), 1)) - >>> period('year', '2014-2-3').offset('first-of', 'month') - Period(('year', Instant((2014, 2, 1)), 1)) - >>> period('year', '2014-2-3').offset('first-of', 'year') - Period(('year', Instant((2014, 1, 1)), 1)) - - >>> period('day', '2014-2-3').offset('last-of', 'month') - Period(('day', Instant((2014, 2, 28)), 1)) - >>> period('day', '2014-2-3').offset('last-of', 'year') - Period(('day', Instant((2014, 12, 31)), 1)) - - >>> period('day', '2014-2-3', 4).offset('last-of', 'month') - Period(('day', Instant((2014, 2, 28)), 4)) - >>> period('day', '2014-2-3', 4).offset('last-of', 'year') - Period(('day', Instant((2014, 12, 31)), 4)) - - >>> period('month', '2014-2-3').offset('last-of') - Period(('month', Instant((2014, 2, 28)), 1)) - >>> period('month', '2014-2-3').offset('last-of', 'month') - Period(('month', Instant((2014, 2, 28)), 1)) - >>> period('month', '2014-2-3').offset('last-of', 'year') - Period(('month', Instant((2014, 12, 31)), 1)) - - >>> period('month', '2014-2-3', 4).offset('last-of') - Period(('month', Instant((2014, 2, 28)), 4)) - >>> period('month', '2014-2-3', 4).offset('last-of', 'month') - Period(('month', Instant((2014, 2, 28)), 4)) - >>> period('month', '2014-2-3', 4).offset('last-of', 'year') - Period(('month', Instant((2014, 12, 31)), 4)) - - >>> period('year', 2014).offset('last-of') - Period(('year', Instant((2014, 12, 31)), 1)) - >>> period('year', 2014).offset('last-of', 'month') - Period(('year', Instant((2014, 1, 31)), 1)) - >>> period('year', 2014).offset('last-of', 'year') - Period(('year', Instant((2014, 12, 31)), 1)) - - >>> period('year', '2014-2-3').offset('last-of') - Period(('year', Instant((2014, 12, 31)), 1)) - >>> period('year', '2014-2-3').offset('last-of', 'month') - Period(('year', Instant((2014, 2, 28)), 1)) - >>> period('year', '2014-2-3').offset('last-of', 'year') - Period(('year', Instant((2014, 12, 31)), 1)) - """ + return self.__class__((self[0], self[1].offset(offset, self[0] if unit is None else unit), self[2])) def contains(self, other: Period) -> bool: + """Returns ``True`` if the period contains ``other``. + + For instance, ``period(2015)`` contains ``period(2015-01)``. + """ - Returns ``True`` if the period contains ``other``. For instance, ``period(2015)`` contains ``period(2015-01)`` - """ + return self.start <= other.start and self.stop >= other.stop @property def size(self): - """ - Return the size of the period. + """Return the size of the period.""" - >>> period('month', '2012-2-29', 4).size - 4 - """ return self[2] @property def size_in_months(self): - """ - Return the size of the period in months. + """Return the size of the period in months.""" - >>> period('month', '2012-2-29', 4).size_in_months - 4 - >>> period('year', '2012', 1).size_in_months - 12 - """ if (self[0] == config.MONTH): return self[2] if(self[0] == config.YEAR): @@ -354,14 +470,8 @@ def size_in_months(self): @property def size_in_days(self): - """ - Return the size of the period in days. + """Return the size of the period in days.""" - >>> period('month', '2012-2-29', 4).size_in_days - 28 - >>> period('year', '2012', 1).size_in_days - 366 - """ unit, instant, length = self if unit == config.DAY: @@ -374,40 +484,44 @@ def size_in_days(self): @property def start(self) -> Instant: - """ - Return the first day of the period as an Instant instance. + """Return the first day of the period as an Instant instance.""" - >>> period('month', '2012-2-29', 4).start - Instant((2012, 2, 29)) - """ return self[1] @property def stop(self) -> Instant: + """Return the last day of the period as an Instant instance. + + Examples: + >>> Period(("year", Instant((2022, 1, 1)), 1)).stop + Instant((2022, 12, 31)) + + >>> Period(("month", Instant((2022, 1, 1)), 12)).stop + Instant((2022, 12, 31)) + + >>> Period(("day", Instant((2022, 1, 1)), 365)).stop + Instant((2022, 12, 31)) + + >>> Period(("year", Instant((2012, 2, 29)), 1)).stop + Instant((2013, 2, 28)) + + >>> Period(("month", Instant((2012, 2, 29)), 1)).stop + Instant((2012, 3, 28)) + + >>> Period(("day", Instant((2012, 2, 29)), 1)).stop + Instant((2012, 2, 29)) + + >>> Period(("year", Instant((2012, 2, 29)), 2)).stop + Instant((2014, 2, 28)) + + >>> Period(("month", Instant((2012, 2, 29)), 2)).stop + Instant((2012, 4, 28)) + + >>> Period(("day", Instant((2012, 2, 29)), 2)).stop + Instant((2012, 3, 1)) + """ - Return the last day of the period as an Instant instance. - - >>> period('year', 2014).stop - Instant((2014, 12, 31)) - >>> period('month', 2014).stop - Instant((2014, 12, 31)) - >>> period('day', 2014).stop - Instant((2014, 12, 31)) - - >>> period('year', '2012-2-29').stop - Instant((2013, 2, 28)) - >>> period('month', '2012-2-29').stop - Instant((2012, 3, 28)) - >>> period('day', '2012-2-29').stop - Instant((2012, 2, 29)) - - >>> period('year', '2012-2-29', 2).stop - Instant((2014, 2, 28)) - >>> period('month', '2012-2-29', 2).stop - Instant((2012, 4, 28)) - >>> period('day', '2012-2-29', 2).stop - Instant((2012, 3, 1)) - """ + unit, start_instant, size = self year, month, day = start_instant if unit == config.ETERNITY: diff --git a/setup.py b/setup.py index 38837bacb1..68d0b0f029 100644 --- a/setup.py +++ b/setup.py @@ -56,7 +56,7 @@ 'flake8-rst-docstrings == 0.2.3', 'mypy == 0.910', 'openapi-spec-validator >= 0.3.0', - 'pycodestyle >= 2.8.0, < 2.9.0', + 'pycodestyle >= 2.7.0, < 2.8.0', 'pylint == 2.10.2', 'xdoctest >= 1.0.0, < 2.0.0', ] + api_requirements From 1b8cfecc37e42c19b98e33587cbafaebd006eaef Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Thu, 28 Jul 2022 21:44:54 +0200 Subject: [PATCH 30/70] Fix periods' helpers doctests --- openfisca_core/periods/config.py | 21 +- openfisca_core/periods/helpers.py | 348 ++++++++++++++++++++--------- openfisca_core/periods/instant_.py | 42 ++-- openfisca_core/periods/period_.py | 107 +++------ openfisca_core/types/_domain.py | 13 ++ 5 files changed, 327 insertions(+), 204 deletions(-) diff --git a/openfisca_core/periods/config.py b/openfisca_core/periods/config.py index 6e0c698098..657831d527 100644 --- a/openfisca_core/periods/config.py +++ b/openfisca_core/periods/config.py @@ -1,15 +1,18 @@ +from __future__ import annotations + +from typing import Dict, Pattern + import re -import typing -DAY = 'day' -MONTH = 'month' -YEAR = 'year' -ETERNITY = 'eternity' +DAY = "day" +MONTH = "month" +YEAR = "year" +ETERNITY = "eternity" # Matches "2015", "2015-01", "2015-01-01" # Does not match "2015-13", "2015-12-32" -INSTANT_PATTERN = re.compile(r"^\d{4}(-(0[1-9]|1[012]))?(-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01]))?$") +INSTANT_PATTERN: Pattern = re.compile(r"^\d{4}(-(0[1-9]|1[012]))?(-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01]))?$") -date_by_instant_cache: typing.Dict = {} -str_by_instant_cache: typing.Dict = {} -year_or_month_or_day_re = re.compile(r'(18|19|20)\d{2}(-(0?[1-9]|1[0-2])(-([0-2]?\d|3[0-1]))?)?$') +date_by_instant_cache: Dict = {} +str_by_instant_cache: Dict = {} +year_or_month_or_day_re: Pattern = re.compile(r'(18|19|20)\d{2}(-(0?[1-9]|1[0-2])(-([0-2]?\d|3[0-1]))?)?$') diff --git a/openfisca_core/periods/helpers.py b/openfisca_core/periods/helpers.py index e4f93e4edb..994e126b40 100644 --- a/openfisca_core/periods/helpers.py +++ b/openfisca_core/periods/helpers.py @@ -1,186 +1,302 @@ -from typing import Dict +from __future__ import annotations + +from typing import Any, Dict, NoReturn, Optional import datetime import os +from openfisca_core import types + from . import config from .instant_ import Instant from .period_ import Period -def instant(instant): - """Return a new instant, aka a triple of integers (year, month, day). +def instant(value: Any) -> Optional[types.Instant]: + """Build a new instant, aka a triple of integers (year, month, day). + + Args: + value: An ``instant-like`` object. + + Returns: + None: When ``instant`` is None. + :obj:`.Instant`: Otherwise. + + Raises: + ValueError: When the arguments were invalid, like "2021-32-13". + + Examples: + >>> instant(datetime.date(2021, 9, 16)) + Instant((2021, 9, 16)) - >>> instant(2014) - Instant((2014, 1, 1)) - >>> instant('2014') - Instant((2014, 1, 1)) - >>> instant('2014-02') - Instant((2014, 2, 1)) - >>> instant('2014-3-2') - Instant((2014, 3, 2)) - >>> instant(instant('2014-3-2')) - Instant((2014, 3, 2)) - >>> instant(period('month', '2014-3-2')) - Instant((2014, 3, 2)) + >>> instant(Instant((2021, 9, 16))) + Instant((2021, 9, 16)) + + >>> instant(Period(("year", Instant((2021, 9, 16)), 1))) + Instant((2021, 9, 16)) + + >>> instant("2021") + Instant((2021, 1, 1)) + + >>> instant(2021) + Instant((2021, 1, 1)) + + >>> instant((2021, 9)) + Instant((2021, 9, 1)) - >>> instant(None) """ - if instant is None: + + if value is None: return None - if isinstance(instant, Instant): - return instant - if isinstance(instant, str): - if not config.INSTANT_PATTERN.match(instant): - raise ValueError("'{}' is not a valid instant. Instants are described using the 'YYYY-MM-DD' format, for instance '2015-06-15'.".format(instant)) + + if isinstance(value, Instant): + return value + + if isinstance(value, str): + if not config.INSTANT_PATTERN.match(value): + raise ValueError(f"'{value}' is not a valid instant. Instants are described using the 'YYYY-MM-DD' format, for instance '2015-06-15'.") + instant = Instant( int(fragment) - for fragment in instant.split('-', 2)[:3] + for fragment in value.split('-', 2)[:3] ) - elif isinstance(instant, datetime.date): - instant = Instant((instant.year, instant.month, instant.day)) - elif isinstance(instant, int): - instant = (instant,) - elif isinstance(instant, list): - assert 1 <= len(instant) <= 3 - instant = tuple(instant) - elif isinstance(instant, Period): - instant = instant.start + + elif isinstance(value, datetime.date): + instant = Instant((value.year, value.month, value.day)) + + elif isinstance(value, int): + instant = (value,) + + elif isinstance(value, list): + assert 1 <= len(value) <= 3 + instant = tuple(value) + + elif isinstance(value, Period): + instant = value.start + else: - assert isinstance(instant, tuple), instant - assert 1 <= len(instant) <= 3 + assert isinstance(value, tuple), value + assert 1 <= len(value) <= 3 + instant = value + if len(instant) == 1: return Instant((instant[0], 1, 1)) + if len(instant) == 2: return Instant((instant[0], instant[1], 1)) + return Instant(instant) -def instant_date(instant): +def instant_date(instant: Optional[types.Instant]) -> Optional[datetime.date]: + """Returns the date representation of an ``Instant``. + + Args: + instant (:obj:`.Instant`, optional): + An ``instant`` to get the date from. + + Returns: + None: When ``instant`` is None. + datetime.date: Otherwise. + + Examples: + >>> instant_date(Instant((2021, 1, 1))) + datetime.date(2021, 1, 1) + + """ + if instant is None: return None + instant_date = config.date_by_instant_cache.get(instant) + if instant_date is None: config.date_by_instant_cache[instant] = instant_date = datetime.date(*instant) + return instant_date -def period(value) -> Period: - """Return a new period, aka a triple (unit, start_instant, size). +def period(value: Any) -> types.Period: + """Build a new period, aka a triple (unit, start_instant, size). + + Args: + value: A ``period-like`` object. + + Returns: + :obj:`.Period`: A period. - >>> period('2014') - Period((YEAR, Instant((2014, 1, 1)), 1)) - >>> period('year:2014') - Period((YEAR, Instant((2014, 1, 1)), 1)) + Raises: + :exc:`ValueError`: When the arguments were invalid, like "2021-32-13". - >>> period('2014-2') - Period((MONTH, Instant((2014, 2, 1)), 1)) - >>> period('2014-02') - Period((MONTH, Instant((2014, 2, 1)), 1)) - >>> period('month:2014-2') - Period((MONTH, Instant((2014, 2, 1)), 1)) + Examples: + >>> period(Period(("year", Instant((2021, 1, 1)), 1))) + Period(('year', Instant((2021, 1, 1)), 1)) + + >>> period(Instant((2021, 1, 1))) + Period(('day', Instant((2021, 1, 1)), 1)) + + >>> period("eternity") + Period(('eternity', Instant((1, 1, 1)), inf)) + + >>> period(2021) + Period(('year', Instant((2021, 1, 1)), 1)) + + >>> period("2014") + Period(('year', Instant((2014, 1, 1)), 1)) + + >>> period("year:2014") + Period(('year', Instant((2014, 1, 1)), 1)) + + >>> period("month:2014-2") + Period(('month', Instant((2014, 2, 1)), 1)) + + >>> period("year:2014-2") + Period(('year', Instant((2014, 2, 1)), 1)) + + >>> period("day:2014-2-2") + Period(('day', Instant((2014, 2, 2)), 1)) + + >>> period("day:2014-2-2:3") + Period(('day', Instant((2014, 2, 2)), 3)) - >>> period('year:2014-2') - Period((YEAR, Instant((2014, 2, 1)), 1)) """ + if isinstance(value, Period): return value if isinstance(value, Instant): return Period((config.DAY, value, 1)) - def parse_simple_period(value): - """ - Parses simple periods respecting the ISO format, such as 2012 or 2015-03 - """ - try: - date = datetime.datetime.strptime(value, '%Y') - except ValueError: - try: - date = datetime.datetime.strptime(value, '%Y-%m') - except ValueError: - try: - date = datetime.datetime.strptime(value, '%Y-%m-%d') - except ValueError: - return None - else: - return Period((config.DAY, Instant((date.year, date.month, date.day)), 1)) - else: - return Period((config.MONTH, Instant((date.year, date.month, 1)), 1)) - else: - return Period((config.YEAR, Instant((date.year, date.month, 1)), 1)) - - def raise_error(value): - message = os.linesep.join([ - "Expected a period (eg. '2017', '2017-01', '2017-01-01', ...); got: '{}'.".format(value), - "Learn more about legal period formats in OpenFisca:", - "." - ]) - raise ValueError(message) + if value == "ETERNITY" or value == config.ETERNITY: + return Period(("eternity", instant(datetime.date.min), float("inf"))) - if value == 'ETERNITY' or value == config.ETERNITY: - return Period(('eternity', instant(datetime.date.min), float("inf"))) - - # check the type if isinstance(value, int): return Period((config.YEAR, Instant((value, 1, 1)), 1)) + if not isinstance(value, str): - raise_error(value) + _raise_error(value) + + # Try to parse as a simple period + period = _parse_simple_period(value) - # try to parse as a simple period - period = parse_simple_period(value) if period is not None: return period - # complex period must have a ':' in their strings + # Complex periods must have a ':' in their strings if ":" not in value: - raise_error(value) + _raise_error(value) components = value.split(':') - # left-most component must be a valid unit + # Left-most component must be a valid unit unit = components[0] + if unit not in (config.DAY, config.MONTH, config.YEAR): - raise_error(value) + _raise_error(value) + + # Middle component must be a valid iso period + base_period = _parse_simple_period(components[1]) - # middle component must be a valid iso period - base_period = parse_simple_period(components[1]) if not base_period: - raise_error(value) + _raise_error(value) - # period like year:2015-03 have a size of 1 + # Periods like year:2015-03 have a size of 1 if len(components) == 2: size = 1 - # if provided, make sure the size is an integer + + # If provided, make sure the size is an integer elif len(components) == 3: try: size = int(components[2]) + except ValueError: - raise_error(value) - # if there is more than 2 ":" in the string, the period is invalid + _raise_error(value) + + # If there are more than 2 ":" in the string, the period is invalid else: - raise_error(value) + _raise_error(value) - # reject ambiguous period such as month:2014 + # Reject ambiguous periods such as month:2014 if unit_weight(base_period.unit) > unit_weight(unit): - raise_error(value) + _raise_error(value) return Period((unit, base_period.start, size)) -def key_period_size(period): +def _parse_simple_period(value: str) -> Optional[types.Period]: + """Parse simple periods respecting the ISO format. + + Such as "2012" or "2015-03". + + Examples: + >>> _parse_simple_period("2022") + Period(('year', Instant((2022, 1, 1)), 1)) + + >>> _parse_simple_period("2022-02") + Period(('month', Instant((2022, 2, 1)), 1)) + + >>> _parse_simple_period("2022-02-13") + Period(('day', Instant((2022, 2, 13)), 1)) + """ - Defines a key in order to sort periods by length. It uses two aspects : first unit then size - :param period: an OpenFisca period - :return: a string + try: + date = datetime.datetime.strptime(value, '%Y') + except ValueError: + try: + date = datetime.datetime.strptime(value, '%Y-%m') + except ValueError: + try: + date = datetime.datetime.strptime(value, '%Y-%m-%d') + except ValueError: + return None + else: + return Period((config.DAY, Instant((date.year, date.month, date.day)), 1)) + else: + return Period((config.MONTH, Instant((date.year, date.month, 1)), 1)) + else: + return Period((config.YEAR, Instant((date.year, date.month, 1)), 1)) + - >>> key_period_size(period('2014')) - '2_1' - >>> key_period_size(period('2013')) - '2_1' - >>> key_period_size(period('2014-01')) - '1_1' +def _raise_error(value: str) -> NoReturn: + """Raise an error. + + Examples: + >>> _raise_error("Oi mate!") + Traceback (most recent call last): + ValueError: Expected a period (eg. '2017', '2017-01', '2017-01-01', ...); got: 'Oi mate!'. + + """ + + message = os.linesep.join([ + "Expected a period (eg. '2017', '2017-01', '2017-01-01', ...); got: '{}'.".format(value), + "Learn more about legal period formats in OpenFisca:", + "." + ]) + raise ValueError(message) + + +def key_period_size(period: types.Period) -> str: + """Define a key in order to sort periods by length. + + It uses two aspects: first, ``unit``, then, ``size``. + + Args: + period: An :mod:`.openfisca_core` :obj:`.Period`. + + Returns: + :obj:`str`: A string. + + Examples: + >>> instant = Instant((2021, 9, 14)) + + >>> period = Period(("day", instant, 1)) + >>> key_period_size(period) + '100_1' + + >>> period = Period(("year", instant, 3)) + >>> key_period_size(period) + '300_3' """ @@ -190,6 +306,14 @@ def key_period_size(period): def unit_weights() -> Dict[str, int]: + """Assign weights to date units. + + Examples: + >>> unit_weights() + {'day': 100, ...} + + """ + return { config.DAY: 100, config.MONTH: 200, @@ -199,4 +323,12 @@ def unit_weights() -> Dict[str, int]: def unit_weight(unit: str) -> int: + """Retrieves a specific date unit weight. + + Examples: + >>> unit_weight("day") + 100 + + """ + return unit_weights()[unit] diff --git a/openfisca_core/periods/instant_.py b/openfisca_core/periods/instant_.py index 48c3a1d995..63cc63636a 100644 --- a/openfisca_core/periods/instant_.py +++ b/openfisca_core/periods/instant_.py @@ -1,8 +1,12 @@ from __future__ import annotations +from typing import Union + import calendar import datetime +from openfisca_core import types + from . import config @@ -16,7 +20,7 @@ class Instant(tuple): :obj:`instants <.Instant>` can be thought of as "day dates". Args: - tuple(tuple(int, int, int)): + (tuple(tuple(int, int, int))): The ``year``, ``month``, and ``day``, accordingly. Examples: @@ -80,31 +84,39 @@ class Instant(tuple): """ - def __repr__(self): - return '{}({})'.format(self.__class__.__name__, super(Instant, self).__repr__()) + def __repr__(self) -> str: + return f"{self.__class__.__name__}({super(Instant, self).__repr__()})" - def __str__(self): + def __str__(self) -> str: instant_str = config.str_by_instant_cache.get(self) + if instant_str is None: config.str_by_instant_cache[self] = instant_str = self.date.isoformat() + return instant_str @property - def date(self): - instant_date = config.date_by_instant_cache.get(self) - if instant_date is None: - config.date_by_instant_cache[self] = instant_date = datetime.date(*self) - return instant_date + def year(self) -> int: + return self[0] + + @property + def month(self) -> int: + return self[1] @property - def day(self): + def day(self) -> int: return self[2] @property - def month(self): - return self[1] + def date(self) -> datetime.date: + instant_date = config.date_by_instant_cache.get(self) + + if instant_date is None: + config.date_by_instant_cache[self] = instant_date = datetime.date(*self) - def offset(self, offset, unit): + return instant_date + + def offset(self, offset: Union[str, int], unit: str) -> types.Instant: """Increments/decrements the given instant with offset units. Args: @@ -189,7 +201,3 @@ def offset(self, offset, unit): day = month_last_day return self.__class__((year, month, day)) - - @property - def year(self): - return self[0] diff --git a/openfisca_core/periods/period_.py b/openfisca_core/periods/period_.py index 9f2d9c29f9..464ea51020 100644 --- a/openfisca_core/periods/period_.py +++ b/openfisca_core/periods/period_.py @@ -1,6 +1,11 @@ from __future__ import annotations +from typing import Optional, Sequence, Union + import calendar +import datetime + +from openfisca_core import types from . import config, helpers from .instant_ import Instant @@ -12,17 +17,15 @@ class Period(tuple): A :class:`.Period` is a triple (``unit``, ``start``, ``size``). Attributes: - unit (:obj:`.DateUnit`): - Either an :meth:`~DateUnit.isoformat` unit (``day``, ``month``, - ``year``), an :meth:`~DateUnit.isocalendar` one (``week_day``, - ``week``, ``year``), or :obj:`~DateUnit.ETERNITY`. + unit (:obj:`str`): + Either ``year``, ``month``, ``day`` or ``eternity``. start (:obj:`.Instant`): The "instant" the :obj:`.Period` starts at. size (:obj:`int`): The amount of ``unit``, starting at ``start``, at least ``1``. Args: - fragments (tuple(.DateUnit, .Instant, int)): + (tuple(tuple(str, .Instant, int))): The ``unit``, ``start``, and ``size``, accordingly. Examples: @@ -125,10 +128,10 @@ class Period(tuple): """ - def __repr__(self): - return '{}({})'.format(self.__class__.__name__, super(Period, self).__repr__()) + def __repr__(self) -> str: + return f"{self.__class__.__name__}({super(Period, self).__repr__()})" - def __str__(self): + def __str__(self) -> str: """Transform period to a string. Examples: @@ -191,57 +194,17 @@ def __str__(self): return '{}:{}-{:02d}:{}'.format(unit, year, month, size) @property - def date(self): + def date(self) -> datetime.date: assert self.size == 1, '"date" is undefined for a period of size > 1: {}'.format(self) return self.start.date @property - def days(self): + def days(self) -> int: """Count the number of days in period.""" return (self.stop.date - self.start.date).days + 1 - def intersection(self, start, stop): - if start is None and stop is None: - return self - period_start = self[1] - period_stop = self.stop - if start is None: - start = period_start - if stop is None: - stop = period_stop - if stop < period_start or period_stop < start: - return None - intersection_start = max(period_start, start) - intersection_stop = min(period_stop, stop) - if intersection_start == period_start and intersection_stop == period_stop: - return self - if intersection_start.day == 1 and intersection_start.month == 1 \ - and intersection_stop.day == 31 and intersection_stop.month == 12: - return self.__class__(( - 'year', - intersection_start, - intersection_stop.year - intersection_start.year + 1, - )) - if intersection_start.day == 1 and intersection_stop.day == calendar.monthrange(intersection_stop.year, - intersection_stop.month)[1]: - return self.__class__(( - 'month', - intersection_start, - ( - (intersection_stop.year - intersection_start.year) * 12 - + intersection_stop.month - - intersection_start.month - + 1 - ), - )) - return self.__class__(( - 'day', - intersection_start, - (intersection_stop.date - intersection_start.date).days + 1, - )) - - def get_subperiods(self, unit): + def get_subperiods(self, unit: str) -> Sequence[types.Period]: """Return the list of all the periods of unit ``unit`` contained in self. Examples: @@ -267,7 +230,11 @@ def get_subperiods(self, unit): if unit == config.DAY: return [self.first_day.offset(i, config.DAY) for i in range(self.size_in_days)] - def offset(self, offset, unit = None): + def offset( + self, + offset: Union[str, int], + unit: Optional[str] = None, + ) -> types.Period: """Increment (or decrement) the given period with offset units. Examples: @@ -443,7 +410,7 @@ def offset(self, offset, unit = None): return self.__class__((self[0], self[1].offset(offset, self[0] if unit is None else unit), self[2])) - def contains(self, other: Period) -> bool: + def contains(self, other: types.Period) -> bool: """Returns ``True`` if the period contains ``other``. For instance, ``period(2015)`` contains ``period(2015-01)``. @@ -453,13 +420,13 @@ def contains(self, other: Period) -> bool: return self.start <= other.start and self.stop >= other.stop @property - def size(self): + def size(self) -> int: """Return the size of the period.""" return self[2] @property - def size_in_months(self): + def size_in_months(self) -> int: """Return the size of the period in months.""" if (self[0] == config.MONTH): @@ -469,7 +436,7 @@ def size_in_months(self): raise ValueError("Cannot calculate number of months in {0}".format(self[0])) @property - def size_in_days(self): + def size_in_days(self) -> int: """Return the size of the period in days.""" unit, instant, length = self @@ -483,13 +450,13 @@ def size_in_days(self): raise ValueError("Cannot calculate number of days in {0}".format(unit)) @property - def start(self) -> Instant: + def start(self) -> types.Instant: """Return the first day of the period as an Instant instance.""" return self[1] @property - def stop(self) -> Instant: + def stop(self) -> types.Instant: """Return the last day of the period as an Instant instance. Examples: @@ -570,34 +537,34 @@ def unit(self) -> str: # Reference periods @property - def last_month(self) -> Period: + def last_month(self) -> types.Period: return self.first_month.offset(-1) @property - def last_3_months(self) -> Period: - start: Instant = self.first_month.start + def last_3_months(self) -> types.Period: + start: types.Instant = self.first_month.start return self.__class__((config.MONTH, start, 3)).offset(-3) @property - def last_year(self) -> Period: - start: Instant = self.start.offset("first-of", config.YEAR) + def last_year(self) -> types.Period: + start: types.Instant = self.start.offset("first-of", config.YEAR) return self.__class__((config.YEAR, start, 1)).offset(-1) @property - def n_2(self) -> Period: - start: Instant = self.start.offset("first-of", config.YEAR) + def n_2(self) -> types.Period: + start: types.Instant = self.start.offset("first-of", config.YEAR) return self.__class__((config.YEAR, start, 1)).offset(-2) @property - def this_year(self) -> Period: - start: Instant = self.start.offset("first-of", config.YEAR) + def this_year(self) -> types.Period: + start: types.Instant = self.start.offset("first-of", config.YEAR) return self.__class__((config.YEAR, start, 1)) @property - def first_month(self) -> Period: - start: Instant = self.start.offset("first-of", config.MONTH) + def first_month(self) -> types.Period: + start: types.Instant = self.start.offset("first-of", config.MONTH) return self.__class__((config.MONTH, start, 1)) @property - def first_day(self) -> Period: + def first_day(self) -> types.Period: return self.__class__((config.DAY, self.start, 1)) diff --git a/openfisca_core/types/_domain.py b/openfisca_core/types/_domain.py index 643f27964f..3c64682d08 100644 --- a/openfisca_core/types/_domain.py +++ b/openfisca_core/types/_domain.py @@ -58,6 +58,10 @@ def get_memory_usage(self) -> Any: class Instant(Protocol): """Instant protocol.""" + @abc.abstractmethod + def offset(self, offset: Any, unit: Any) -> Any: + """Abstract method.""" + @typing_extensions.runtime_checkable class ParameterNodeAtInstant(Protocol): @@ -80,11 +84,20 @@ class Period(Protocol): @abc.abstractmethod def start(self) -> Any: """Abstract method.""" + @property @abc.abstractmethod def unit(self) -> Any: """Abstract method.""" + @abc.abstractmethod + def offset(self, offset: Any, unit: Any = None) -> Any: + """Abstract method.""" + + @abc.abstractmethod + def stop(self) -> Any: + """Abstract method.""" + class Population(Protocol): """Population protocol.""" From 09fab88a74aa9e8fe9255b00f2a2a57dbc415455 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Sat, 30 Jul 2022 15:20:06 +0200 Subject: [PATCH 31/70] Add tests to periods.instant --- openfisca_core/periods/tests/__init__.py | 0 openfisca_core/periods/tests/test_helpers.py | 64 ++++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 openfisca_core/periods/tests/__init__.py create mode 100644 openfisca_core/periods/tests/test_helpers.py diff --git a/openfisca_core/periods/tests/__init__.py b/openfisca_core/periods/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openfisca_core/periods/tests/test_helpers.py b/openfisca_core/periods/tests/test_helpers.py new file mode 100644 index 0000000000..c20445b72d --- /dev/null +++ b/openfisca_core/periods/tests/test_helpers.py @@ -0,0 +1,64 @@ +import datetime + +import pytest + +from openfisca_core import periods +from openfisca_core.periods import DateUnit, Instant, Period + + +def test_instant(): + assert periods.instant((2022, 1, 1)) == Instant((2022, 1, 1)) + + +@pytest.mark.parametrize("arg, expected", [ + [None, None], + [datetime.date(1, 1, 1), Instant((1, 1, 1))], + [Instant((1, 1, 1)), Instant((1, 1, 1))], + [Period((DateUnit.DAY, Instant((1, 1, 1)), 365)), Instant((1, 1, 1))], + [-1, Instant((-1, 1, 1))], + [0, Instant((0, 1, 1))], + [1, Instant((1, 1, 1))], + [999, Instant((999, 1, 1))], + [1000, Instant((1000, 1, 1))], + ["1000", Instant((1000, 1, 1))], + ["1000-01-01", Instant((1000, 1, 1))], + [(None,), Instant((None, 1, 1))], + [(None, None), Instant((None, None, 1))], + [(None, None, None), Instant((None, None, None))], + [(datetime.date(1, 1, 1),), Instant((datetime.date(1, 1, 1), 1, 1))], + [(Instant((1, 1, 1)),), Instant((Instant((1, 1, 1)), 1, 1))], + [(Period((DateUnit.DAY, Instant((1, 1, 1)), 365)),), Instant((Period((DateUnit.DAY, Instant((1, 1, 1)), 365)), 1, 1))], + [(-1,), Instant((-1, 1, 1))], + [(-1, -1), Instant((-1, -1, 1))], + [(-1, -1, -1), Instant((-1, -1, -1))], + [("-1",), Instant(("-1", 1, 1))], + [("-1", "-1"), Instant(("-1", "-1", 1))], + [("-1", "-1", "-1"), Instant(("-1", "-1", "-1"))], + [("1-1",), Instant(("1-1", 1, 1))], + [("1-1-1",), Instant(("1-1-1", 1, 1))], + ]) +def test_instant_with_a_valid_argument(arg, expected): + assert periods.instant(arg) == expected + + +@pytest.mark.parametrize("arg, error", [ + ["1000-0", ValueError], + ["1000-0-0", ValueError], + ["1000-1", ValueError], + ["1000-1-1", ValueError], + ["1", ValueError], + ["a", ValueError], + ["999", ValueError], + ["1:1000-01-01", ValueError], + ["a:1000-01-01", ValueError], + ["year:1000-01-01", ValueError], + ["year:1000-01-01:1", ValueError], + ["year:1000-01-01:3", ValueError], + ["1000-01-01:a", ValueError], + ["1000-01-01:1", ValueError], + [(), AssertionError], + [(None, None, None, None), AssertionError], + ]) +def test_instant_with_an_invalid_argument(arg, error): + with pytest.raises(error): + periods.instant(arg) \ No newline at end of file From 2ecbdb8d4905d2e12fea3b898c31e6dcf07cc576 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Sat, 30 Jul 2022 16:17:00 +0200 Subject: [PATCH 32/70] Add tests to periods.instant_date --- .../periods/tests/helpers/__init__.py | 0 .../test_instant.py} | 6 +-- .../tests/helpers/test_instant_date.py | 42 +++++++++++++++++++ 3 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 openfisca_core/periods/tests/helpers/__init__.py rename openfisca_core/periods/tests/{test_helpers.py => helpers/test_instant.py} (94%) create mode 100644 openfisca_core/periods/tests/helpers/test_instant_date.py diff --git a/openfisca_core/periods/tests/helpers/__init__.py b/openfisca_core/periods/tests/helpers/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openfisca_core/periods/tests/test_helpers.py b/openfisca_core/periods/tests/helpers/test_instant.py similarity index 94% rename from openfisca_core/periods/tests/test_helpers.py rename to openfisca_core/periods/tests/helpers/test_instant.py index c20445b72d..c96a467f54 100644 --- a/openfisca_core/periods/tests/test_helpers.py +++ b/openfisca_core/periods/tests/helpers/test_instant.py @@ -7,7 +7,7 @@ def test_instant(): - assert periods.instant((2022, 1, 1)) == Instant((2022, 1, 1)) + assert periods.instant((2022, 1, 1)) == Instant((2022, 1, 1)) @pytest.mark.parametrize("arg, expected", [ @@ -44,7 +44,7 @@ def test_instant_with_a_valid_argument(arg, expected): @pytest.mark.parametrize("arg, error", [ ["1000-0", ValueError], ["1000-0-0", ValueError], - ["1000-1", ValueError], + ["1000-1", ValueError], ["1000-1-1", ValueError], ["1", ValueError], ["a", ValueError], @@ -61,4 +61,4 @@ def test_instant_with_a_valid_argument(arg, expected): ]) def test_instant_with_an_invalid_argument(arg, error): with pytest.raises(error): - periods.instant(arg) \ No newline at end of file + periods.instant(arg) diff --git a/openfisca_core/periods/tests/helpers/test_instant_date.py b/openfisca_core/periods/tests/helpers/test_instant_date.py new file mode 100644 index 0000000000..bd1872b079 --- /dev/null +++ b/openfisca_core/periods/tests/helpers/test_instant_date.py @@ -0,0 +1,42 @@ +import datetime + +import pytest + +from openfisca_core import periods +from openfisca_core.periods import DateUnit, Instant, Period + + +def test_instant_date(): + assert periods.instant_date((2022, 1, 1)) == datetime.date(2022, 1, 1) + + +@pytest.mark.parametrize("arg, expected", [ + [None, None], + [Instant((1, 1, 1)), datetime.date(1, 1, 1)], + [Instant((4, 2, 29)), datetime.date(4, 2, 29)], + [(1, 1, 1), datetime.date(1, 1, 1)], + ]) +def test_instant_date_with_a_valid_argument(arg, expected): + assert periods.instant_date(arg) == expected + + +@pytest.mark.parametrize("arg, error", [ + [datetime.date(1, 1, 1), TypeError], + [Instant((-1, 1, 1)), ValueError], + [Instant((1, -1, 1)), ValueError], + [Instant((1, 13, -1)), ValueError], + [Instant((1, 1, -1)), ValueError], + [Instant((1, 1, 32)), ValueError], + [Instant((1, 2, 29)), ValueError], + [Instant(("1", 1, 1)), TypeError], + [Period((DateUnit.YEAR, Instant((1, 1, 1)), 1)), TypeError], + [1, TypeError], + ["1", TypeError], + [(), TypeError], + [(Instant((1, 1, 1)),), TypeError], + [(1,), TypeError], + [(1, 1), TypeError], + ]) +def test_instant_date_with_an_invalid_argument(arg, error): + with pytest.raises(error): + periods.instant_date(arg) From 0e51ac5b6a61657c90fbc6b25cf35d0b2dfb9523 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Sat, 30 Jul 2022 16:37:19 +0200 Subject: [PATCH 33/70] Add tests to periods.key_period_size --- .../periods/tests/helpers/test_instant.py | 4 --- .../tests/helpers/test_instant_date.py | 4 --- .../tests/helpers/test_key_period_size.py | 32 +++++++++++++++++++ 3 files changed, 32 insertions(+), 8 deletions(-) create mode 100644 openfisca_core/periods/tests/helpers/test_key_period_size.py diff --git a/openfisca_core/periods/tests/helpers/test_instant.py b/openfisca_core/periods/tests/helpers/test_instant.py index c96a467f54..d05e39ef5c 100644 --- a/openfisca_core/periods/tests/helpers/test_instant.py +++ b/openfisca_core/periods/tests/helpers/test_instant.py @@ -6,10 +6,6 @@ from openfisca_core.periods import DateUnit, Instant, Period -def test_instant(): - assert periods.instant((2022, 1, 1)) == Instant((2022, 1, 1)) - - @pytest.mark.parametrize("arg, expected", [ [None, None], [datetime.date(1, 1, 1), Instant((1, 1, 1))], diff --git a/openfisca_core/periods/tests/helpers/test_instant_date.py b/openfisca_core/periods/tests/helpers/test_instant_date.py index bd1872b079..474628a76c 100644 --- a/openfisca_core/periods/tests/helpers/test_instant_date.py +++ b/openfisca_core/periods/tests/helpers/test_instant_date.py @@ -6,10 +6,6 @@ from openfisca_core.periods import DateUnit, Instant, Period -def test_instant_date(): - assert periods.instant_date((2022, 1, 1)) == datetime.date(2022, 1, 1) - - @pytest.mark.parametrize("arg, expected", [ [None, None], [Instant((1, 1, 1)), datetime.date(1, 1, 1)], diff --git a/openfisca_core/periods/tests/helpers/test_key_period_size.py b/openfisca_core/periods/tests/helpers/test_key_period_size.py new file mode 100644 index 0000000000..52f372f1dd --- /dev/null +++ b/openfisca_core/periods/tests/helpers/test_key_period_size.py @@ -0,0 +1,32 @@ +import datetime + +import pytest + +from openfisca_core import periods +from openfisca_core.periods import DateUnit, Instant, Period + + +@pytest.mark.parametrize("arg, expected", [ + [Period((DateUnit.DAY, Instant((1, 1, 1)), 365)), "100_365"], + [Period((DateUnit.MONTH, Instant((1, 1, 1)), 12)), "200_12"], + [Period((DateUnit.YEAR, Instant((1, 1, 1)), 2)), "300_2"], + [Period((DateUnit.ETERNITY, Instant((1, 1, 1)), 1)), "400_1"], + [(DateUnit.DAY, None, 1), "100_1"], + [(DateUnit.MONTH, None, -1000), "200_-1000"], + ]) +def test_key_period_size_with_a_valid_argument(arg, expected): + assert periods.key_period_size(arg) == expected + + +@pytest.mark.parametrize("arg, error", [ + [None, TypeError], + [Instant((1, 1, 1)), KeyError], + [1, TypeError], + ["1", ValueError], + ["111", KeyError], + [(), ValueError], + [(1, 1, 1), KeyError], + ]) +def test_key_period_size_with_an_invalid_argument(arg, error): + with pytest.raises(error): + periods.key_period_size(arg) From a7e9851c13fec5fdd10e07d2d521a6cd679921e9 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Sat, 30 Jul 2022 16:43:01 +0200 Subject: [PATCH 34/70] Add doctest to periods.unit_weight --- .../periods/tests/helpers/test_instant.py | 6 +++--- .../periods/tests/helpers/test_instant_date.py | 4 ++-- .../tests/helpers/test_key_period_size.py | 16 +++++++--------- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/openfisca_core/periods/tests/helpers/test_instant.py b/openfisca_core/periods/tests/helpers/test_instant.py index d05e39ef5c..d147fcdbf9 100644 --- a/openfisca_core/periods/tests/helpers/test_instant.py +++ b/openfisca_core/periods/tests/helpers/test_instant.py @@ -3,14 +3,14 @@ import pytest from openfisca_core import periods -from openfisca_core.periods import DateUnit, Instant, Period +from openfisca_core.periods import Instant, Period @pytest.mark.parametrize("arg, expected", [ [None, None], [datetime.date(1, 1, 1), Instant((1, 1, 1))], [Instant((1, 1, 1)), Instant((1, 1, 1))], - [Period((DateUnit.DAY, Instant((1, 1, 1)), 365)), Instant((1, 1, 1))], + [Period((periods.DAY, Instant((1, 1, 1)), 365)), Instant((1, 1, 1))], [-1, Instant((-1, 1, 1))], [0, Instant((0, 1, 1))], [1, Instant((1, 1, 1))], @@ -23,7 +23,7 @@ [(None, None, None), Instant((None, None, None))], [(datetime.date(1, 1, 1),), Instant((datetime.date(1, 1, 1), 1, 1))], [(Instant((1, 1, 1)),), Instant((Instant((1, 1, 1)), 1, 1))], - [(Period((DateUnit.DAY, Instant((1, 1, 1)), 365)),), Instant((Period((DateUnit.DAY, Instant((1, 1, 1)), 365)), 1, 1))], + [(Period((periods.DAY, Instant((1, 1, 1)), 365)),), Instant((Period((periods.DAY, Instant((1, 1, 1)), 365)), 1, 1))], [(-1,), Instant((-1, 1, 1))], [(-1, -1), Instant((-1, -1, 1))], [(-1, -1, -1), Instant((-1, -1, -1))], diff --git a/openfisca_core/periods/tests/helpers/test_instant_date.py b/openfisca_core/periods/tests/helpers/test_instant_date.py index 474628a76c..22bd459d0f 100644 --- a/openfisca_core/periods/tests/helpers/test_instant_date.py +++ b/openfisca_core/periods/tests/helpers/test_instant_date.py @@ -3,7 +3,7 @@ import pytest from openfisca_core import periods -from openfisca_core.periods import DateUnit, Instant, Period +from openfisca_core.periods import Instant, Period @pytest.mark.parametrize("arg, expected", [ @@ -25,7 +25,7 @@ def test_instant_date_with_a_valid_argument(arg, expected): [Instant((1, 1, 32)), ValueError], [Instant((1, 2, 29)), ValueError], [Instant(("1", 1, 1)), TypeError], - [Period((DateUnit.YEAR, Instant((1, 1, 1)), 1)), TypeError], + [Period((periods.YEAR, Instant((1, 1, 1)), 1)), TypeError], [1, TypeError], ["1", TypeError], [(), TypeError], diff --git a/openfisca_core/periods/tests/helpers/test_key_period_size.py b/openfisca_core/periods/tests/helpers/test_key_period_size.py index 52f372f1dd..6f8acc17c9 100644 --- a/openfisca_core/periods/tests/helpers/test_key_period_size.py +++ b/openfisca_core/periods/tests/helpers/test_key_period_size.py @@ -1,18 +1,16 @@ -import datetime - import pytest from openfisca_core import periods -from openfisca_core.periods import DateUnit, Instant, Period +from openfisca_core.periods import Instant, Period @pytest.mark.parametrize("arg, expected", [ - [Period((DateUnit.DAY, Instant((1, 1, 1)), 365)), "100_365"], - [Period((DateUnit.MONTH, Instant((1, 1, 1)), 12)), "200_12"], - [Period((DateUnit.YEAR, Instant((1, 1, 1)), 2)), "300_2"], - [Period((DateUnit.ETERNITY, Instant((1, 1, 1)), 1)), "400_1"], - [(DateUnit.DAY, None, 1), "100_1"], - [(DateUnit.MONTH, None, -1000), "200_-1000"], + [Period((periods.DAY, Instant((1, 1, 1)), 365)), "100_365"], + [Period((periods.MONTH, Instant((1, 1, 1)), 12)), "200_12"], + [Period((periods.YEAR, Instant((1, 1, 1)), 2)), "300_2"], + [Period((periods.ETERNITY, Instant((1, 1, 1)), 1)), "400_1"], + [(periods.DAY, None, 1), "100_1"], + [(periods.MONTH, None, -1000), "200_-1000"], ]) def test_key_period_size_with_a_valid_argument(arg, expected): assert periods.key_period_size(arg) == expected From aff6d23f06640a87c08efba5aa93c6efbaa7f7ba Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Sat, 30 Jul 2022 17:40:45 +0200 Subject: [PATCH 35/70] Extract parse simple period from builder --- openfisca_core/periods/period_.py | 8 ++++---- .../helpers/test__parse_simple_period.py | 19 +++++++++++++++++++ .../periods/tests/helpers/test_instant.py | 5 +++++ .../tests/helpers/test_instant_date.py | 8 +------- .../tests/helpers/test_key_period_size.py | 14 -------------- .../periods/tests/helpers/test_period.py | 0 6 files changed, 29 insertions(+), 25 deletions(-) create mode 100644 openfisca_core/periods/tests/helpers/test__parse_simple_period.py create mode 100644 openfisca_core/periods/tests/helpers/test_period.py diff --git a/openfisca_core/periods/period_.py b/openfisca_core/periods/period_.py index 464ea51020..d3a9170f8d 100644 --- a/openfisca_core/periods/period_.py +++ b/openfisca_core/periods/period_.py @@ -43,7 +43,7 @@ class Period(tuple): >>> dict([period, instant]) Traceback (most recent call last): - ValueError: dictionary update sequence element #0 has length 3; 2 is required + ValueError: dictionary update sequence element #0 has length 3... >>> list(period) ['year', Instant((2021, 9, 1)), 3] @@ -205,16 +205,16 @@ def days(self) -> int: return (self.stop.date - self.start.date).days + 1 def get_subperiods(self, unit: str) -> Sequence[types.Period]: - """Return the list of all the periods of unit ``unit`` contained in self. + """Return the list of all the periods of unit ``unit``. Examples: >>> period = Period(("year", Instant((2021, 1, 1)), 1)) >>> period.get_subperiods("month") - [Period(('month', Instant((2021, 1, 1)), 1)), ...2021, 12, 1)), 1))] + [Period(('month', Instant((2021, 1, 1)), 1)),...2021, 12, 1)), 1))] >>> period = Period(("year", Instant((2021, 1, 1)), 2)) >>> period.get_subperiods("year") - [Period(('year', Instant((2021, 1, 1)), 1)), ...((2022, 1, 1)), 1))] + [Period(('year', Instant((2021, 1, 1)), 1)),...((2022, 1, 1)), 1))] """ diff --git a/openfisca_core/periods/tests/helpers/test__parse_simple_period.py b/openfisca_core/periods/tests/helpers/test__parse_simple_period.py new file mode 100644 index 0000000000..081d795e85 --- /dev/null +++ b/openfisca_core/periods/tests/helpers/test__parse_simple_period.py @@ -0,0 +1,19 @@ +import pytest + +from openfisca_core import periods +from openfisca_core.periods import Instant, Period, helpers + + +@pytest.mark.parametrize("arg, expected", [ + ["1", None], + ["999", None], + ["1000", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], + ["1000-1", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], + ["1000-01", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], + ["1000-1-1", Period((periods.DAY, Instant((1000, 1, 1)), 1))], + ["1000-01-1", Period((periods.DAY, Instant((1000, 1, 1)), 1))], + ["1000-01-01", Period((periods.DAY, Instant((1000, 1, 1)), 1))], + ["1000-01-99", None], + ]) +def test__parse_simple_period_with_a_valid_argument(arg, expected): + assert helpers._parse_simple_period(arg) == expected diff --git a/openfisca_core/periods/tests/helpers/test_instant.py b/openfisca_core/periods/tests/helpers/test_instant.py index d147fcdbf9..dd6002c73f 100644 --- a/openfisca_core/periods/tests/helpers/test_instant.py +++ b/openfisca_core/periods/tests/helpers/test_instant.py @@ -17,6 +17,7 @@ [999, Instant((999, 1, 1))], [1000, Instant((1000, 1, 1))], ["1000", Instant((1000, 1, 1))], + ["1000-01", Instant((1000, 1, 1))], ["1000-01-01", Instant((1000, 1, 1))], [(None,), Instant((None, 1, 1))], [(None, None), Instant((None, None, 1))], @@ -38,12 +39,16 @@ def test_instant_with_a_valid_argument(arg, expected): @pytest.mark.parametrize("arg, error", [ + [periods.YEAR, ValueError], + [periods.ETERNITY, ValueError], ["1000-0", ValueError], ["1000-0-0", ValueError], ["1000-1", ValueError], ["1000-1-1", ValueError], ["1", ValueError], ["a", ValueError], + ["year", ValueError], + ["eternity", ValueError], ["999", ValueError], ["1:1000-01-01", ValueError], ["a:1000-01-01", ValueError], diff --git a/openfisca_core/periods/tests/helpers/test_instant_date.py b/openfisca_core/periods/tests/helpers/test_instant_date.py index 22bd459d0f..722728a6e4 100644 --- a/openfisca_core/periods/tests/helpers/test_instant_date.py +++ b/openfisca_core/periods/tests/helpers/test_instant_date.py @@ -3,7 +3,7 @@ import pytest from openfisca_core import periods -from openfisca_core.periods import Instant, Period +from openfisca_core.periods import Instant @pytest.mark.parametrize("arg, expected", [ @@ -17,7 +17,6 @@ def test_instant_date_with_a_valid_argument(arg, expected): @pytest.mark.parametrize("arg, error", [ - [datetime.date(1, 1, 1), TypeError], [Instant((-1, 1, 1)), ValueError], [Instant((1, -1, 1)), ValueError], [Instant((1, 13, -1)), ValueError], @@ -25,11 +24,6 @@ def test_instant_date_with_a_valid_argument(arg, expected): [Instant((1, 1, 32)), ValueError], [Instant((1, 2, 29)), ValueError], [Instant(("1", 1, 1)), TypeError], - [Period((periods.YEAR, Instant((1, 1, 1)), 1)), TypeError], - [1, TypeError], - ["1", TypeError], - [(), TypeError], - [(Instant((1, 1, 1)),), TypeError], [(1,), TypeError], [(1, 1), TypeError], ]) diff --git a/openfisca_core/periods/tests/helpers/test_key_period_size.py b/openfisca_core/periods/tests/helpers/test_key_period_size.py index 6f8acc17c9..1094d4e42e 100644 --- a/openfisca_core/periods/tests/helpers/test_key_period_size.py +++ b/openfisca_core/periods/tests/helpers/test_key_period_size.py @@ -14,17 +14,3 @@ ]) def test_key_period_size_with_a_valid_argument(arg, expected): assert periods.key_period_size(arg) == expected - - -@pytest.mark.parametrize("arg, error", [ - [None, TypeError], - [Instant((1, 1, 1)), KeyError], - [1, TypeError], - ["1", ValueError], - ["111", KeyError], - [(), ValueError], - [(1, 1, 1), KeyError], - ]) -def test_key_period_size_with_an_invalid_argument(arg, error): - with pytest.raises(error): - periods.key_period_size(arg) diff --git a/openfisca_core/periods/tests/helpers/test_period.py b/openfisca_core/periods/tests/helpers/test_period.py new file mode 100644 index 0000000000..e69de29bb2 From 039468de0c648e20149865e3278d3a2436a3e54c Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Sat, 30 Jul 2022 18:16:21 +0200 Subject: [PATCH 36/70] Add tests to periods.period --- .../periods/tests/helpers/test_period.py | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/openfisca_core/periods/tests/helpers/test_period.py b/openfisca_core/periods/tests/helpers/test_period.py index e69de29bb2..4df00a9a18 100644 --- a/openfisca_core/periods/tests/helpers/test_period.py +++ b/openfisca_core/periods/tests/helpers/test_period.py @@ -0,0 +1,69 @@ +import datetime + +import pytest + +from openfisca_core import periods +from openfisca_core.periods import Instant, Period + + +@pytest.mark.parametrize("arg, expected", [ + ["eternity", Period((periods.ETERNITY, Instant((1, 1, 1)), float("inf")))], + ["ETERNITY", Period((periods.ETERNITY, Instant((1, 1, 1)), float("inf")))], + [periods.ETERNITY, Period((periods.ETERNITY, Instant((1, 1, 1)), float("inf")))], + [Instant((1, 1, 1)), Period((periods.DAY, Instant((1, 1, 1)), 1))], + [Period((periods.DAY, Instant((1, 1, 1)), 365)), Period((periods.DAY, Instant((1, 1, 1)), 365))], + [-1, Period((periods.YEAR, Instant((-1, 1, 1)), 1))], + [0, Period((periods.YEAR, Instant((0, 1, 1)), 1))], + [1, Period((periods.YEAR, Instant((1, 1, 1)), 1))], + [999, Period((periods.YEAR, Instant((999, 1, 1)), 1))], + [1000, Period((periods.YEAR, Instant((1000, 1, 1)), 1))], + ["1000", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], + ["1000-1", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], + ["1000-1-1", Period((periods.DAY, Instant((1000, 1, 1)), 1))], + ["1000-01", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], + ["1000-01-01", Period((periods.DAY, Instant((1000, 1, 1)), 1))], + ["year:1000-01-01", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], + ["year:1000-01-01:1", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], + ["year:1000-01-01:3", Period((periods.YEAR, Instant((1000, 1, 1)), 3))], + ]) +def test_instant_with_a_valid_argument(arg, expected): + assert periods.period(arg) == expected + + +@pytest.mark.parametrize("arg, error", [ + [None, ValueError], + [periods.YEAR, ValueError], + [datetime.date(1, 1, 1), ValueError], + ["1000-0", ValueError], + ["1000-0-0", ValueError], + ["1", ValueError], + ["a", ValueError], + ["year", ValueError], + ["999", ValueError], + ["1:1000-01-01", ValueError], + ["a:1000-01-01", ValueError], + ["1000-01-01:a", ValueError], + ["1000-01-01:1", ValueError], + [(), ValueError], + [(None,), ValueError], + [(None, None), ValueError], + [(None, None, None), ValueError], + [(None, None, None, None), ValueError], + [(datetime.date(1, 1, 1),), ValueError], + [(Instant((1, 1, 1)),), ValueError], + [(Period((periods.DAY, Instant((1, 1, 1)), 365)),), ValueError], + [(1,), ValueError], + [(1, 1), ValueError], + [(1, 1, 1), ValueError], + [(-1,), ValueError], + [(-1, -1), ValueError], + [(-1, -1, -1), ValueError], + [("-1",), ValueError], + [("-1", "-1"), ValueError], + [("-1", "-1", "-1"), ValueError], + [("1-1",), ValueError], + [("1-1-1",), ValueError], + ]) +def test_instant_with_an_invalid_argument(arg, error): + with pytest.raises(error): + periods.period(arg) From 85717fadcafc1a3a3ff8727a2a2633db6aabaeae Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Sat, 30 Jul 2022 18:44:02 +0200 Subject: [PATCH 37/70] Refactor period's str year tests --- .../periods/tests/period/__init__.py | 0 .../periods/tests/period/test_str.py | 25 +++++++++++++++++++ tests/core/test_periods.py | 20 --------------- 3 files changed, 25 insertions(+), 20 deletions(-) create mode 100644 openfisca_core/periods/tests/period/__init__.py create mode 100644 openfisca_core/periods/tests/period/test_str.py diff --git a/openfisca_core/periods/tests/period/__init__.py b/openfisca_core/periods/tests/period/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openfisca_core/periods/tests/period/test_str.py b/openfisca_core/periods/tests/period/test_str.py new file mode 100644 index 0000000000..45a2e588cb --- /dev/null +++ b/openfisca_core/periods/tests/period/test_str.py @@ -0,0 +1,25 @@ +import pytest + +from openfisca_core.periods import DateUnit, Instant, Period + + +@pytest.fixture +def first_jan(): + return Instant((2022, 1, 1)) + + +@pytest.fixture +def first_march(): + return Instant((2022, 3, 1)) + + +@pytest.mark.parametrize("date_unit, instant, size, expected", [ + [DateUnit.YEAR, Instant((2022, 1, 1)), 1, "2022"], + [DateUnit.MONTH, Instant((2022, 1, 1)), 12, "2022"], + [DateUnit.YEAR, Instant((2022, 3, 1)), 1, "year:2022-03"], + [DateUnit.MONTH, Instant((2022, 3, 1)), 12, "year:2022-03"], + [DateUnit.YEAR, Instant((2022, 1, 1)), 3, "year:2022:3"], + [DateUnit.YEAR, Instant((2022, 1, 3)), 3, "year:2022:3"], + ]) +def test_str_with_years(date_unit, instant, size, expected): + assert str(Period((date_unit, instant, size))) == expected \ No newline at end of file diff --git a/tests/core/test_periods.py b/tests/core/test_periods.py index 2c125d527c..9e23b0d782 100644 --- a/tests/core/test_periods.py +++ b/tests/core/test_periods.py @@ -14,26 +14,6 @@ ''' -# Years - -def test_year(): - assert str(Period((YEAR, first_jan, 1))) == '2014' - - -def test_12_months_is_a_year(): - assert str(Period((MONTH, first_jan, 12))) == '2014' - - -def test_rolling_year(): - assert str(Period((MONTH, first_march, 12))) == 'year:2014-03' - assert str(Period((YEAR, first_march, 1))) == 'year:2014-03' - - -def test_several_years(): - assert str(Period((YEAR, first_jan, 3))) == 'year:2014:3' - assert str(Period((YEAR, first_march, 3))) == 'year:2014-03:3' - - # Months def test_month(): From 3fc64b6464ddaab681c69f6ff8486c50f4e37773 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Sat, 30 Jul 2022 18:46:23 +0200 Subject: [PATCH 38/70] Refactor period's str month tests --- .../periods/tests/period/test_str.py | 19 +++++++++---------- tests/core/test_periods.py | 7 ------- 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/openfisca_core/periods/tests/period/test_str.py b/openfisca_core/periods/tests/period/test_str.py index 45a2e588cb..f96102547b 100644 --- a/openfisca_core/periods/tests/period/test_str.py +++ b/openfisca_core/periods/tests/period/test_str.py @@ -3,16 +3,6 @@ from openfisca_core.periods import DateUnit, Instant, Period -@pytest.fixture -def first_jan(): - return Instant((2022, 1, 1)) - - -@pytest.fixture -def first_march(): - return Instant((2022, 3, 1)) - - @pytest.mark.parametrize("date_unit, instant, size, expected", [ [DateUnit.YEAR, Instant((2022, 1, 1)), 1, "2022"], [DateUnit.MONTH, Instant((2022, 1, 1)), 12, "2022"], @@ -22,4 +12,13 @@ def first_march(): [DateUnit.YEAR, Instant((2022, 1, 3)), 3, "year:2022:3"], ]) def test_str_with_years(date_unit, instant, size, expected): + assert str(Period((date_unit, instant, size))) == expected + + +@pytest.mark.parametrize("date_unit, instant, size, expected", [ + [DateUnit.MONTH, Instant((2022, 1, 1)), 1, "2022-01"], + [DateUnit.MONTH, Instant((2022, 1, 1)), 3, "month:2022-01:3"], + [DateUnit.MONTH, Instant((2022, 3, 1)), 3, "month:2022-03:3"], + ]) +def test_str_with_months(date_unit, instant, size, expected): assert str(Period((date_unit, instant, size))) == expected \ No newline at end of file diff --git a/tests/core/test_periods.py b/tests/core/test_periods.py index 9e23b0d782..4ab5a3f91f 100644 --- a/tests/core/test_periods.py +++ b/tests/core/test_periods.py @@ -16,13 +16,6 @@ # Months -def test_month(): - assert str(Period((MONTH, first_jan, 1))) == '2014-01' - - -def test_several_months(): - assert str(Period((MONTH, first_jan, 3))) == 'month:2014-01:3' - assert str(Period((MONTH, first_march, 3))) == 'month:2014-03:3' # Days From 7503d2719bd650e5ce2e96b241bb69dc241baa24 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Sat, 30 Jul 2022 18:48:23 +0200 Subject: [PATCH 39/70] Refactor period's str day tests --- .../periods/tests/period/test_str.py | 9 ++++++++ tests/core/test_periods.py | 21 ------------------- 2 files changed, 9 insertions(+), 21 deletions(-) diff --git a/openfisca_core/periods/tests/period/test_str.py b/openfisca_core/periods/tests/period/test_str.py index f96102547b..47ea3c390d 100644 --- a/openfisca_core/periods/tests/period/test_str.py +++ b/openfisca_core/periods/tests/period/test_str.py @@ -21,4 +21,13 @@ def test_str_with_years(date_unit, instant, size, expected): [DateUnit.MONTH, Instant((2022, 3, 1)), 3, "month:2022-03:3"], ]) def test_str_with_months(date_unit, instant, size, expected): + assert str(Period((date_unit, instant, size))) == expected + + +@pytest.mark.parametrize("date_unit, instant, size, expected", [ + [DateUnit.DAY, Instant((2022, 1, 1)), 1, "2022-01-01"], + [DateUnit.DAY, Instant((2022, 1, 1)), 3, "day:2022-01-01:3"], + [DateUnit.DAY, Instant((2022, 3, 1)), 3, "day:2022-03-01:3"], + ]) +def test_str_with_days(date_unit, instant, size, expected): assert str(Period((date_unit, instant, size))) == expected \ No newline at end of file diff --git a/tests/core/test_periods.py b/tests/core/test_periods.py index 4ab5a3f91f..92c8b49022 100644 --- a/tests/core/test_periods.py +++ b/tests/core/test_periods.py @@ -9,31 +9,10 @@ first_march = Instant((2014, 3, 1)) -''' -Test Period -> String -''' - - -# Months - - - -# Days - -def test_day(): - assert str(Period((DAY, first_jan, 1))) == '2014-01-01' - - -def test_several_days(): - assert str(Period((DAY, first_jan, 3))) == 'day:2014-01-01:3' - assert str(Period((DAY, first_march, 3))) == 'day:2014-03-01:3' - - ''' Test String -> Period ''' - # Years def test_parsing_year(): From fed7c4a8d1008a1deb6416b4f185cc90224eb2db Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Sat, 30 Jul 2022 18:54:46 +0200 Subject: [PATCH 40/70] Remove redundant examples --- openfisca_core/periods/period_.py | 4 +++- openfisca_core/periods/tests/period/test_str.py | 2 +- tests/core/test_periods.py | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/openfisca_core/periods/period_.py b/openfisca_core/periods/period_.py index d3a9170f8d..5d5b0f58b6 100644 --- a/openfisca_core/periods/period_.py +++ b/openfisca_core/periods/period_.py @@ -165,8 +165,10 @@ def __str__(self) -> str: """ unit, start_instant, size = self + if unit == config.ETERNITY: - return 'ETERNITY' + return "ETERNITY" + year, month, day = start_instant # 1 year long period diff --git a/openfisca_core/periods/tests/period/test_str.py b/openfisca_core/periods/tests/period/test_str.py index 47ea3c390d..874a8405ab 100644 --- a/openfisca_core/periods/tests/period/test_str.py +++ b/openfisca_core/periods/tests/period/test_str.py @@ -30,4 +30,4 @@ def test_str_with_months(date_unit, instant, size, expected): [DateUnit.DAY, Instant((2022, 3, 1)), 3, "day:2022-03-01:3"], ]) def test_str_with_days(date_unit, instant, size, expected): - assert str(Period((date_unit, instant, size))) == expected \ No newline at end of file + assert str(Period((date_unit, instant, size))) == expected diff --git a/tests/core/test_periods.py b/tests/core/test_periods.py index 92c8b49022..58fffd7919 100644 --- a/tests/core/test_periods.py +++ b/tests/core/test_periods.py @@ -15,6 +15,7 @@ # Years + def test_parsing_year(): assert period('2014') == Period((YEAR, first_jan, 1)) From bcd1edc223bca789b5e2b15441880012b8d0a875 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Sat, 30 Jul 2022 19:35:31 +0200 Subject: [PATCH 41/70] Redistribute tests --- openfisca_core/periods/period_.py | 1 - .../periods/tests/helpers/test_instant.py | 2 + .../periods/tests/helpers/test_period.py | 35 ++++- .../periods/tests/period/test_size_in_days.py | 20 +++ .../periods/tests/period/test_str.py | 27 ++-- tests/core/test_periods.py | 132 +----------------- 6 files changed, 68 insertions(+), 149 deletions(-) create mode 100644 openfisca_core/periods/tests/period/test_size_in_days.py diff --git a/openfisca_core/periods/period_.py b/openfisca_core/periods/period_.py index 5d5b0f58b6..20c301a8b7 100644 --- a/openfisca_core/periods/period_.py +++ b/openfisca_core/periods/period_.py @@ -203,7 +203,6 @@ def date(self) -> datetime.date: @property def days(self) -> int: """Count the number of days in period.""" - return (self.stop.date - self.start.date).days + 1 def get_subperiods(self, unit: str) -> Sequence[types.Period]: diff --git a/openfisca_core/periods/tests/helpers/test_instant.py b/openfisca_core/periods/tests/helpers/test_instant.py index dd6002c73f..c58c5897f2 100644 --- a/openfisca_core/periods/tests/helpers/test_instant.py +++ b/openfisca_core/periods/tests/helpers/test_instant.py @@ -58,6 +58,8 @@ def test_instant_with_a_valid_argument(arg, expected): ["1000-01-01:a", ValueError], ["1000-01-01:1", ValueError], [(), AssertionError], + [{}, AssertionError], + ["", ValueError], [(None, None, None, None), AssertionError], ]) def test_instant_with_an_invalid_argument(arg, error): diff --git a/openfisca_core/periods/tests/helpers/test_period.py b/openfisca_core/periods/tests/helpers/test_period.py index 4df00a9a18..50cc59eae8 100644 --- a/openfisca_core/periods/tests/helpers/test_period.py +++ b/openfisca_core/periods/tests/helpers/test_period.py @@ -22,9 +22,24 @@ ["1000-1-1", Period((periods.DAY, Instant((1000, 1, 1)), 1))], ["1000-01", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], ["1000-01-01", Period((periods.DAY, Instant((1000, 1, 1)), 1))], + ["1004-02-29", Period((periods.DAY, Instant((1004, 2, 29)), 1))], + ["year:1000", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], + ["year:1000-01", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], ["year:1000-01-01", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], + ["year:1000:1", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], + ["year:1000-01:1", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], ["year:1000-01-01:1", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], - ["year:1000-01-01:3", Period((periods.YEAR, Instant((1000, 1, 1)), 3))], + ["year:1000:3", Period((periods.YEAR, Instant((1000, 1, 1)), 3))], + ["year:1000-01:3", Period((periods.YEAR, Instant((1000, 1, 1)), 3))], + ["month:1000-01-01:3", Period((periods.MONTH, Instant((1000, 1, 1)), 3))], + ["month:1000-01", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], + ["month:1000-01-01", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], + ["month:1000-01:1", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], + ["month:1000-01:3", Period((periods.MONTH, Instant((1000, 1, 1)), 3))], + ["month:1000-01-01:3", Period((periods.MONTH, Instant((1000, 1, 1)), 3))], + ["month:1000-01-01:3", Period((periods.MONTH, Instant((1000, 1, 1)), 3))], + ["day:1000-01-01", Period((periods.DAY, Instant((1000, 1, 1)), 1))], + ["day:1000-01-01:3", Period((periods.DAY, Instant((1000, 1, 1)), 3))], ]) def test_instant_with_a_valid_argument(arg, expected): assert periods.period(arg) == expected @@ -35,16 +50,28 @@ def test_instant_with_a_valid_argument(arg, expected): [periods.YEAR, ValueError], [datetime.date(1, 1, 1), ValueError], ["1000-0", ValueError], + ["1000-13", ValueError], ["1000-0-0", ValueError], + ["1000-1-0", ValueError], + ["1000-2-31", ValueError], ["1", ValueError], ["a", ValueError], ["year", ValueError], ["999", ValueError], - ["1:1000-01-01", ValueError], - ["a:1000-01-01", ValueError], - ["1000-01-01:a", ValueError], + ["1:1000", ValueError], + ["a:1000", ValueError], + ["month:1000", ValueError], + ["day:1000-01", ValueError], + ["1000:a", ValueError], + ["1000:1", ValueError], + ["1000-01:1", ValueError], ["1000-01-01:1", ValueError], + ["month:1000:1", ValueError], + ["day:1000:1", ValueError], + ["day:1000-01:1", ValueError], [(), ValueError], + [{}, ValueError], + ["", ValueError], [(None,), ValueError], [(None, None), ValueError], [(None, None, None), ValueError], diff --git a/openfisca_core/periods/tests/period/test_size_in_days.py b/openfisca_core/periods/tests/period/test_size_in_days.py new file mode 100644 index 0000000000..c68d5d82b0 --- /dev/null +++ b/openfisca_core/periods/tests/period/test_size_in_days.py @@ -0,0 +1,20 @@ +import pytest + +from openfisca_core import periods +from openfisca_core.periods import Instant, Period + + +@pytest.mark.parametrize("date_unit, instant, size, expected", [ + [periods.DAY, Instant((2022, 12, 31)), 1, 1], + [periods.DAY, Instant((2022, 12, 31)), 3, 3], + [periods.MONTH, Instant((2022, 12, 1)), 1, 31], + [periods.MONTH, Instant((2012, 2, 3)), 1, 29], + [periods.MONTH, Instant((2022, 1, 3)), 3, 31 + 28 + 31], + [periods.MONTH, Instant((2012, 1, 3)), 3, 31 + 29 + 31], + [periods.YEAR, Instant((2022, 12, 1)), 1, 365], + [periods.YEAR, Instant((2012, 1, 1)), 1, 366], + [periods.YEAR, Instant((2022, 1, 1)), 2, 730], + ]) +def test_day_size_in_days(date_unit, instant, size, expected): + period = Period((date_unit, instant, size)) + assert period.size_in_days == expected diff --git a/openfisca_core/periods/tests/period/test_str.py b/openfisca_core/periods/tests/period/test_str.py index 874a8405ab..4e0ba81446 100644 --- a/openfisca_core/periods/tests/period/test_str.py +++ b/openfisca_core/periods/tests/period/test_str.py @@ -1,33 +1,34 @@ import pytest -from openfisca_core.periods import DateUnit, Instant, Period +from openfisca_core import periods +from openfisca_core.periods import Instant, Period @pytest.mark.parametrize("date_unit, instant, size, expected", [ - [DateUnit.YEAR, Instant((2022, 1, 1)), 1, "2022"], - [DateUnit.MONTH, Instant((2022, 1, 1)), 12, "2022"], - [DateUnit.YEAR, Instant((2022, 3, 1)), 1, "year:2022-03"], - [DateUnit.MONTH, Instant((2022, 3, 1)), 12, "year:2022-03"], - [DateUnit.YEAR, Instant((2022, 1, 1)), 3, "year:2022:3"], - [DateUnit.YEAR, Instant((2022, 1, 3)), 3, "year:2022:3"], + [periods.YEAR, Instant((2022, 1, 1)), 1, "2022"], + [periods.MONTH, Instant((2022, 1, 1)), 12, "2022"], + [periods.YEAR, Instant((2022, 3, 1)), 1, "year:2022-03"], + [periods.MONTH, Instant((2022, 3, 1)), 12, "year:2022-03"], + [periods.YEAR, Instant((2022, 1, 1)), 3, "year:2022:3"], + [periods.YEAR, Instant((2022, 1, 3)), 3, "year:2022:3"], ]) def test_str_with_years(date_unit, instant, size, expected): assert str(Period((date_unit, instant, size))) == expected @pytest.mark.parametrize("date_unit, instant, size, expected", [ - [DateUnit.MONTH, Instant((2022, 1, 1)), 1, "2022-01"], - [DateUnit.MONTH, Instant((2022, 1, 1)), 3, "month:2022-01:3"], - [DateUnit.MONTH, Instant((2022, 3, 1)), 3, "month:2022-03:3"], + [periods.MONTH, Instant((2022, 1, 1)), 1, "2022-01"], + [periods.MONTH, Instant((2022, 1, 1)), 3, "month:2022-01:3"], + [periods.MONTH, Instant((2022, 3, 1)), 3, "month:2022-03:3"], ]) def test_str_with_months(date_unit, instant, size, expected): assert str(Period((date_unit, instant, size))) == expected @pytest.mark.parametrize("date_unit, instant, size, expected", [ - [DateUnit.DAY, Instant((2022, 1, 1)), 1, "2022-01-01"], - [DateUnit.DAY, Instant((2022, 1, 1)), 3, "day:2022-01-01:3"], - [DateUnit.DAY, Instant((2022, 3, 1)), 3, "day:2022-03-01:3"], + [periods.DAY, Instant((2022, 1, 1)), 1, "2022-01-01"], + [periods.DAY, Instant((2022, 1, 1)), 3, "day:2022-01-01:3"], + [periods.DAY, Instant((2022, 3, 1)), 3, "day:2022-03-01:3"], ]) def test_str_with_days(date_unit, instant, size, expected): assert str(Period((date_unit, instant, size))) == expected diff --git a/tests/core/test_periods.py b/tests/core/test_periods.py index 58fffd7919..0816a0ce2e 100644 --- a/tests/core/test_periods.py +++ b/tests/core/test_periods.py @@ -3,137 +3,7 @@ import pytest -from openfisca_core.periods import Period, Instant, YEAR, MONTH, DAY, period - -first_jan = Instant((2014, 1, 1)) -first_march = Instant((2014, 3, 1)) - - -''' -Test String -> Period -''' - -# Years - - -def test_parsing_year(): - assert period('2014') == Period((YEAR, first_jan, 1)) - - -def test_parsing_rolling_year(): - assert period('year:2014-03') == Period((YEAR, first_march, 1)) - - -def test_parsing_several_years(): - assert period('year:2014:2') == Period((YEAR, first_jan, 2)) - - -def test_wrong_syntax_several_years(): - with pytest.raises(ValueError): - period('2014:2') - - -# Months - -def test_parsing_month(): - assert period('2014-01') == Period((MONTH, first_jan, 1)) - - -def test_parsing_several_months(): - assert period('month:2014-03:3') == Period((MONTH, first_march, 3)) - - -def test_wrong_syntax_several_months(): - with pytest.raises(ValueError): - period('2014-3:3') - - -# Days - -def test_parsing_day(): - assert period('2014-01-01') == Period((DAY, first_jan, 1)) - - -def test_parsing_several_days(): - assert period('day:2014-03-01:3') == Period((DAY, first_march, 3)) - - -def test_wrong_syntax_several_days(): - with pytest.raises(ValueError): - period('2014-2-3:2') - - -def test_day_size_in_days(): - assert Period(('day', Instant((2014, 12, 31)), 1)).size_in_days == 1 - - -def test_3_day_size_in_days(): - assert Period(('day', Instant((2014, 12, 31)), 3)).size_in_days == 3 - - -def test_month_size_in_days(): - assert Period(('month', Instant((2014, 12, 1)), 1)).size_in_days == 31 - - -def test_leap_month_size_in_days(): - assert Period(('month', Instant((2012, 2, 3)), 1)).size_in_days == 29 - - -def test_3_month_size_in_days(): - assert Period(('month', Instant((2013, 1, 3)), 3)).size_in_days == 31 + 28 + 31 - - -def test_leap_3_month_size_in_days(): - assert Period(('month', Instant((2012, 1, 3)), 3)).size_in_days == 31 + 29 + 31 - - -def test_year_size_in_days(): - assert Period(('year', Instant((2014, 12, 1)), 1)).size_in_days == 365 - - -def test_leap_year_size_in_days(): - assert Period(('year', Instant((2012, 1, 1)), 1)).size_in_days == 366 - - -def test_2_years_size_in_days(): - assert Period(('year', Instant((2014, 1, 1)), 2)).size_in_days == 730 - -# Misc - - -def test_wrong_date(): - with pytest.raises(ValueError): - period("2006-31-03") - - -def test_ambiguous_period(): - with pytest.raises(ValueError): - period('month:2014') - - -def test_deprecated_signature(): - with pytest.raises(TypeError): - period(MONTH, 2014) - - -def test_wrong_argument(): - with pytest.raises(ValueError): - period({}) - - -def test_wrong_argument_1(): - with pytest.raises(ValueError): - period([]) - - -def test_none(): - with pytest.raises(ValueError): - period(None) - - -def test_empty_string(): - with pytest.raises(ValueError): - period('') +from openfisca_core.periods import YEAR, MONTH, DAY, period @pytest.mark.parametrize("test", [ From 309d58ef2a06e63670cdf27f7e74db1b7c67e463 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Sat, 30 Jul 2022 20:26:35 +0200 Subject: [PATCH 42/70] Consolidate tests --- .../periods/tests/helpers/__init__.py | 0 .../helpers/test__parse_simple_period.py | 19 -- .../periods/tests/helpers/test_instant.py | 67 ------ .../tests/helpers/test_instant_date.py | 32 --- .../tests/helpers/test_key_period_size.py | 16 -- .../periods/tests/helpers/test_period.py | 96 -------- .../periods/tests/period/__init__.py | 0 .../periods/tests/period/test_size_in_days.py | 20 -- .../periods/tests/period/test_str.py | 34 --- openfisca_core/periods/tests/test_helpers.py | 210 ++++++++++++++++++ openfisca_core/periods/tests/test_period.py | 80 +++++++ tests/core/test_periods.py | 26 --- 12 files changed, 290 insertions(+), 310 deletions(-) delete mode 100644 openfisca_core/periods/tests/helpers/__init__.py delete mode 100644 openfisca_core/periods/tests/helpers/test__parse_simple_period.py delete mode 100644 openfisca_core/periods/tests/helpers/test_instant.py delete mode 100644 openfisca_core/periods/tests/helpers/test_instant_date.py delete mode 100644 openfisca_core/periods/tests/helpers/test_key_period_size.py delete mode 100644 openfisca_core/periods/tests/helpers/test_period.py delete mode 100644 openfisca_core/periods/tests/period/__init__.py delete mode 100644 openfisca_core/periods/tests/period/test_size_in_days.py delete mode 100644 openfisca_core/periods/tests/period/test_str.py create mode 100644 openfisca_core/periods/tests/test_helpers.py create mode 100644 openfisca_core/periods/tests/test_period.py delete mode 100644 tests/core/test_periods.py diff --git a/openfisca_core/periods/tests/helpers/__init__.py b/openfisca_core/periods/tests/helpers/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/openfisca_core/periods/tests/helpers/test__parse_simple_period.py b/openfisca_core/periods/tests/helpers/test__parse_simple_period.py deleted file mode 100644 index 081d795e85..0000000000 --- a/openfisca_core/periods/tests/helpers/test__parse_simple_period.py +++ /dev/null @@ -1,19 +0,0 @@ -import pytest - -from openfisca_core import periods -from openfisca_core.periods import Instant, Period, helpers - - -@pytest.mark.parametrize("arg, expected", [ - ["1", None], - ["999", None], - ["1000", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], - ["1000-1", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], - ["1000-01", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], - ["1000-1-1", Period((periods.DAY, Instant((1000, 1, 1)), 1))], - ["1000-01-1", Period((periods.DAY, Instant((1000, 1, 1)), 1))], - ["1000-01-01", Period((periods.DAY, Instant((1000, 1, 1)), 1))], - ["1000-01-99", None], - ]) -def test__parse_simple_period_with_a_valid_argument(arg, expected): - assert helpers._parse_simple_period(arg) == expected diff --git a/openfisca_core/periods/tests/helpers/test_instant.py b/openfisca_core/periods/tests/helpers/test_instant.py deleted file mode 100644 index c58c5897f2..0000000000 --- a/openfisca_core/periods/tests/helpers/test_instant.py +++ /dev/null @@ -1,67 +0,0 @@ -import datetime - -import pytest - -from openfisca_core import periods -from openfisca_core.periods import Instant, Period - - -@pytest.mark.parametrize("arg, expected", [ - [None, None], - [datetime.date(1, 1, 1), Instant((1, 1, 1))], - [Instant((1, 1, 1)), Instant((1, 1, 1))], - [Period((periods.DAY, Instant((1, 1, 1)), 365)), Instant((1, 1, 1))], - [-1, Instant((-1, 1, 1))], - [0, Instant((0, 1, 1))], - [1, Instant((1, 1, 1))], - [999, Instant((999, 1, 1))], - [1000, Instant((1000, 1, 1))], - ["1000", Instant((1000, 1, 1))], - ["1000-01", Instant((1000, 1, 1))], - ["1000-01-01", Instant((1000, 1, 1))], - [(None,), Instant((None, 1, 1))], - [(None, None), Instant((None, None, 1))], - [(None, None, None), Instant((None, None, None))], - [(datetime.date(1, 1, 1),), Instant((datetime.date(1, 1, 1), 1, 1))], - [(Instant((1, 1, 1)),), Instant((Instant((1, 1, 1)), 1, 1))], - [(Period((periods.DAY, Instant((1, 1, 1)), 365)),), Instant((Period((periods.DAY, Instant((1, 1, 1)), 365)), 1, 1))], - [(-1,), Instant((-1, 1, 1))], - [(-1, -1), Instant((-1, -1, 1))], - [(-1, -1, -1), Instant((-1, -1, -1))], - [("-1",), Instant(("-1", 1, 1))], - [("-1", "-1"), Instant(("-1", "-1", 1))], - [("-1", "-1", "-1"), Instant(("-1", "-1", "-1"))], - [("1-1",), Instant(("1-1", 1, 1))], - [("1-1-1",), Instant(("1-1-1", 1, 1))], - ]) -def test_instant_with_a_valid_argument(arg, expected): - assert periods.instant(arg) == expected - - -@pytest.mark.parametrize("arg, error", [ - [periods.YEAR, ValueError], - [periods.ETERNITY, ValueError], - ["1000-0", ValueError], - ["1000-0-0", ValueError], - ["1000-1", ValueError], - ["1000-1-1", ValueError], - ["1", ValueError], - ["a", ValueError], - ["year", ValueError], - ["eternity", ValueError], - ["999", ValueError], - ["1:1000-01-01", ValueError], - ["a:1000-01-01", ValueError], - ["year:1000-01-01", ValueError], - ["year:1000-01-01:1", ValueError], - ["year:1000-01-01:3", ValueError], - ["1000-01-01:a", ValueError], - ["1000-01-01:1", ValueError], - [(), AssertionError], - [{}, AssertionError], - ["", ValueError], - [(None, None, None, None), AssertionError], - ]) -def test_instant_with_an_invalid_argument(arg, error): - with pytest.raises(error): - periods.instant(arg) diff --git a/openfisca_core/periods/tests/helpers/test_instant_date.py b/openfisca_core/periods/tests/helpers/test_instant_date.py deleted file mode 100644 index 722728a6e4..0000000000 --- a/openfisca_core/periods/tests/helpers/test_instant_date.py +++ /dev/null @@ -1,32 +0,0 @@ -import datetime - -import pytest - -from openfisca_core import periods -from openfisca_core.periods import Instant - - -@pytest.mark.parametrize("arg, expected", [ - [None, None], - [Instant((1, 1, 1)), datetime.date(1, 1, 1)], - [Instant((4, 2, 29)), datetime.date(4, 2, 29)], - [(1, 1, 1), datetime.date(1, 1, 1)], - ]) -def test_instant_date_with_a_valid_argument(arg, expected): - assert periods.instant_date(arg) == expected - - -@pytest.mark.parametrize("arg, error", [ - [Instant((-1, 1, 1)), ValueError], - [Instant((1, -1, 1)), ValueError], - [Instant((1, 13, -1)), ValueError], - [Instant((1, 1, -1)), ValueError], - [Instant((1, 1, 32)), ValueError], - [Instant((1, 2, 29)), ValueError], - [Instant(("1", 1, 1)), TypeError], - [(1,), TypeError], - [(1, 1), TypeError], - ]) -def test_instant_date_with_an_invalid_argument(arg, error): - with pytest.raises(error): - periods.instant_date(arg) diff --git a/openfisca_core/periods/tests/helpers/test_key_period_size.py b/openfisca_core/periods/tests/helpers/test_key_period_size.py deleted file mode 100644 index 1094d4e42e..0000000000 --- a/openfisca_core/periods/tests/helpers/test_key_period_size.py +++ /dev/null @@ -1,16 +0,0 @@ -import pytest - -from openfisca_core import periods -from openfisca_core.periods import Instant, Period - - -@pytest.mark.parametrize("arg, expected", [ - [Period((periods.DAY, Instant((1, 1, 1)), 365)), "100_365"], - [Period((periods.MONTH, Instant((1, 1, 1)), 12)), "200_12"], - [Period((periods.YEAR, Instant((1, 1, 1)), 2)), "300_2"], - [Period((periods.ETERNITY, Instant((1, 1, 1)), 1)), "400_1"], - [(periods.DAY, None, 1), "100_1"], - [(periods.MONTH, None, -1000), "200_-1000"], - ]) -def test_key_period_size_with_a_valid_argument(arg, expected): - assert periods.key_period_size(arg) == expected diff --git a/openfisca_core/periods/tests/helpers/test_period.py b/openfisca_core/periods/tests/helpers/test_period.py deleted file mode 100644 index 50cc59eae8..0000000000 --- a/openfisca_core/periods/tests/helpers/test_period.py +++ /dev/null @@ -1,96 +0,0 @@ -import datetime - -import pytest - -from openfisca_core import periods -from openfisca_core.periods import Instant, Period - - -@pytest.mark.parametrize("arg, expected", [ - ["eternity", Period((periods.ETERNITY, Instant((1, 1, 1)), float("inf")))], - ["ETERNITY", Period((periods.ETERNITY, Instant((1, 1, 1)), float("inf")))], - [periods.ETERNITY, Period((periods.ETERNITY, Instant((1, 1, 1)), float("inf")))], - [Instant((1, 1, 1)), Period((periods.DAY, Instant((1, 1, 1)), 1))], - [Period((periods.DAY, Instant((1, 1, 1)), 365)), Period((periods.DAY, Instant((1, 1, 1)), 365))], - [-1, Period((periods.YEAR, Instant((-1, 1, 1)), 1))], - [0, Period((periods.YEAR, Instant((0, 1, 1)), 1))], - [1, Period((periods.YEAR, Instant((1, 1, 1)), 1))], - [999, Period((periods.YEAR, Instant((999, 1, 1)), 1))], - [1000, Period((periods.YEAR, Instant((1000, 1, 1)), 1))], - ["1000", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], - ["1000-1", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], - ["1000-1-1", Period((periods.DAY, Instant((1000, 1, 1)), 1))], - ["1000-01", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], - ["1000-01-01", Period((periods.DAY, Instant((1000, 1, 1)), 1))], - ["1004-02-29", Period((periods.DAY, Instant((1004, 2, 29)), 1))], - ["year:1000", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], - ["year:1000-01", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], - ["year:1000-01-01", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], - ["year:1000:1", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], - ["year:1000-01:1", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], - ["year:1000-01-01:1", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], - ["year:1000:3", Period((periods.YEAR, Instant((1000, 1, 1)), 3))], - ["year:1000-01:3", Period((periods.YEAR, Instant((1000, 1, 1)), 3))], - ["month:1000-01-01:3", Period((periods.MONTH, Instant((1000, 1, 1)), 3))], - ["month:1000-01", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], - ["month:1000-01-01", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], - ["month:1000-01:1", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], - ["month:1000-01:3", Period((periods.MONTH, Instant((1000, 1, 1)), 3))], - ["month:1000-01-01:3", Period((periods.MONTH, Instant((1000, 1, 1)), 3))], - ["month:1000-01-01:3", Period((periods.MONTH, Instant((1000, 1, 1)), 3))], - ["day:1000-01-01", Period((periods.DAY, Instant((1000, 1, 1)), 1))], - ["day:1000-01-01:3", Period((periods.DAY, Instant((1000, 1, 1)), 3))], - ]) -def test_instant_with_a_valid_argument(arg, expected): - assert periods.period(arg) == expected - - -@pytest.mark.parametrize("arg, error", [ - [None, ValueError], - [periods.YEAR, ValueError], - [datetime.date(1, 1, 1), ValueError], - ["1000-0", ValueError], - ["1000-13", ValueError], - ["1000-0-0", ValueError], - ["1000-1-0", ValueError], - ["1000-2-31", ValueError], - ["1", ValueError], - ["a", ValueError], - ["year", ValueError], - ["999", ValueError], - ["1:1000", ValueError], - ["a:1000", ValueError], - ["month:1000", ValueError], - ["day:1000-01", ValueError], - ["1000:a", ValueError], - ["1000:1", ValueError], - ["1000-01:1", ValueError], - ["1000-01-01:1", ValueError], - ["month:1000:1", ValueError], - ["day:1000:1", ValueError], - ["day:1000-01:1", ValueError], - [(), ValueError], - [{}, ValueError], - ["", ValueError], - [(None,), ValueError], - [(None, None), ValueError], - [(None, None, None), ValueError], - [(None, None, None, None), ValueError], - [(datetime.date(1, 1, 1),), ValueError], - [(Instant((1, 1, 1)),), ValueError], - [(Period((periods.DAY, Instant((1, 1, 1)), 365)),), ValueError], - [(1,), ValueError], - [(1, 1), ValueError], - [(1, 1, 1), ValueError], - [(-1,), ValueError], - [(-1, -1), ValueError], - [(-1, -1, -1), ValueError], - [("-1",), ValueError], - [("-1", "-1"), ValueError], - [("-1", "-1", "-1"), ValueError], - [("1-1",), ValueError], - [("1-1-1",), ValueError], - ]) -def test_instant_with_an_invalid_argument(arg, error): - with pytest.raises(error): - periods.period(arg) diff --git a/openfisca_core/periods/tests/period/__init__.py b/openfisca_core/periods/tests/period/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/openfisca_core/periods/tests/period/test_size_in_days.py b/openfisca_core/periods/tests/period/test_size_in_days.py deleted file mode 100644 index c68d5d82b0..0000000000 --- a/openfisca_core/periods/tests/period/test_size_in_days.py +++ /dev/null @@ -1,20 +0,0 @@ -import pytest - -from openfisca_core import periods -from openfisca_core.periods import Instant, Period - - -@pytest.mark.parametrize("date_unit, instant, size, expected", [ - [periods.DAY, Instant((2022, 12, 31)), 1, 1], - [periods.DAY, Instant((2022, 12, 31)), 3, 3], - [periods.MONTH, Instant((2022, 12, 1)), 1, 31], - [periods.MONTH, Instant((2012, 2, 3)), 1, 29], - [periods.MONTH, Instant((2022, 1, 3)), 3, 31 + 28 + 31], - [periods.MONTH, Instant((2012, 1, 3)), 3, 31 + 29 + 31], - [periods.YEAR, Instant((2022, 12, 1)), 1, 365], - [periods.YEAR, Instant((2012, 1, 1)), 1, 366], - [periods.YEAR, Instant((2022, 1, 1)), 2, 730], - ]) -def test_day_size_in_days(date_unit, instant, size, expected): - period = Period((date_unit, instant, size)) - assert period.size_in_days == expected diff --git a/openfisca_core/periods/tests/period/test_str.py b/openfisca_core/periods/tests/period/test_str.py deleted file mode 100644 index 4e0ba81446..0000000000 --- a/openfisca_core/periods/tests/period/test_str.py +++ /dev/null @@ -1,34 +0,0 @@ -import pytest - -from openfisca_core import periods -from openfisca_core.periods import Instant, Period - - -@pytest.mark.parametrize("date_unit, instant, size, expected", [ - [periods.YEAR, Instant((2022, 1, 1)), 1, "2022"], - [periods.MONTH, Instant((2022, 1, 1)), 12, "2022"], - [periods.YEAR, Instant((2022, 3, 1)), 1, "year:2022-03"], - [periods.MONTH, Instant((2022, 3, 1)), 12, "year:2022-03"], - [periods.YEAR, Instant((2022, 1, 1)), 3, "year:2022:3"], - [periods.YEAR, Instant((2022, 1, 3)), 3, "year:2022:3"], - ]) -def test_str_with_years(date_unit, instant, size, expected): - assert str(Period((date_unit, instant, size))) == expected - - -@pytest.mark.parametrize("date_unit, instant, size, expected", [ - [periods.MONTH, Instant((2022, 1, 1)), 1, "2022-01"], - [periods.MONTH, Instant((2022, 1, 1)), 3, "month:2022-01:3"], - [periods.MONTH, Instant((2022, 3, 1)), 3, "month:2022-03:3"], - ]) -def test_str_with_months(date_unit, instant, size, expected): - assert str(Period((date_unit, instant, size))) == expected - - -@pytest.mark.parametrize("date_unit, instant, size, expected", [ - [periods.DAY, Instant((2022, 1, 1)), 1, "2022-01-01"], - [periods.DAY, Instant((2022, 1, 1)), 3, "day:2022-01-01:3"], - [periods.DAY, Instant((2022, 3, 1)), 3, "day:2022-03-01:3"], - ]) -def test_str_with_days(date_unit, instant, size, expected): - assert str(Period((date_unit, instant, size))) == expected diff --git a/openfisca_core/periods/tests/test_helpers.py b/openfisca_core/periods/tests/test_helpers.py new file mode 100644 index 0000000000..6dd09788b9 --- /dev/null +++ b/openfisca_core/periods/tests/test_helpers.py @@ -0,0 +1,210 @@ +import datetime + +import pytest + +from openfisca_core import periods +from openfisca_core.periods import Instant, Period, helpers + + +@pytest.mark.parametrize("arg, expected", [ + [None, None], + [datetime.date(1, 1, 1), Instant((1, 1, 1))], + [Instant((1, 1, 1)), Instant((1, 1, 1))], + [Period((periods.DAY, Instant((1, 1, 1)), 365)), Instant((1, 1, 1))], + [-1, Instant((-1, 1, 1))], + [0, Instant((0, 1, 1))], + [1, Instant((1, 1, 1))], + [999, Instant((999, 1, 1))], + [1000, Instant((1000, 1, 1))], + ["1000", Instant((1000, 1, 1))], + ["1000-01", Instant((1000, 1, 1))], + ["1000-01-01", Instant((1000, 1, 1))], + [(None,), Instant((None, 1, 1))], + [(None, None), Instant((None, None, 1))], + [(None, None, None), Instant((None, None, None))], + [(datetime.date(1, 1, 1),), Instant((datetime.date(1, 1, 1), 1, 1))], + [(Instant((1, 1, 1)),), Instant((Instant((1, 1, 1)), 1, 1))], + [(Period((periods.DAY, Instant((1, 1, 1)), 365)),), Instant((Period((periods.DAY, Instant((1, 1, 1)), 365)), 1, 1))], + [(-1,), Instant((-1, 1, 1))], + [(-1, -1), Instant((-1, -1, 1))], + [(-1, -1, -1), Instant((-1, -1, -1))], + [("-1",), Instant(("-1", 1, 1))], + [("-1", "-1"), Instant(("-1", "-1", 1))], + [("-1", "-1", "-1"), Instant(("-1", "-1", "-1"))], + [("1-1",), Instant(("1-1", 1, 1))], + [("1-1-1",), Instant(("1-1-1", 1, 1))], + ]) +def test_instant_with_a_valid_argument(arg, expected): + assert periods.instant(arg) == expected + + +@pytest.mark.parametrize("arg, error", [ + [periods.YEAR, ValueError], + [periods.ETERNITY, ValueError], + ["1000-0", ValueError], + ["1000-0-0", ValueError], + ["1000-1", ValueError], + ["1000-1-1", ValueError], + ["1", ValueError], + ["a", ValueError], + ["year", ValueError], + ["eternity", ValueError], + ["999", ValueError], + ["1:1000-01-01", ValueError], + ["a:1000-01-01", ValueError], + ["year:1000-01-01", ValueError], + ["year:1000-01-01:1", ValueError], + ["year:1000-01-01:3", ValueError], + ["1000-01-01:a", ValueError], + ["1000-01-01:1", ValueError], + [(), AssertionError], + [{}, AssertionError], + ["", ValueError], + [(None, None, None, None), AssertionError], + ]) +def test_instant_with_an_invalid_argument(arg, error): + with pytest.raises(error): + periods.instant(arg) + + +@pytest.mark.parametrize("arg, expected", [ + [None, None], + [Instant((1, 1, 1)), datetime.date(1, 1, 1)], + [Instant((4, 2, 29)), datetime.date(4, 2, 29)], + [(1, 1, 1), datetime.date(1, 1, 1)], + ]) +def test_instant_date_with_a_valid_argument(arg, expected): + assert periods.instant_date(arg) == expected + + +@pytest.mark.parametrize("arg, error", [ + [Instant((-1, 1, 1)), ValueError], + [Instant((1, -1, 1)), ValueError], + [Instant((1, 13, -1)), ValueError], + [Instant((1, 1, -1)), ValueError], + [Instant((1, 1, 32)), ValueError], + [Instant((1, 2, 29)), ValueError], + [Instant(("1", 1, 1)), TypeError], + [(1,), TypeError], + [(1, 1), TypeError], + ]) +def test_instant_date_with_an_invalid_argument(arg, error): + with pytest.raises(error): + periods.instant_date(arg) + + +@pytest.mark.parametrize("arg, expected", [ + ["eternity", Period((periods.ETERNITY, Instant((1, 1, 1)), float("inf")))], + ["ETERNITY", Period((periods.ETERNITY, Instant((1, 1, 1)), float("inf")))], + [periods.ETERNITY, Period((periods.ETERNITY, Instant((1, 1, 1)), float("inf")))], + [Instant((1, 1, 1)), Period((periods.DAY, Instant((1, 1, 1)), 1))], + [Period((periods.DAY, Instant((1, 1, 1)), 365)), Period((periods.DAY, Instant((1, 1, 1)), 365))], + [-1, Period((periods.YEAR, Instant((-1, 1, 1)), 1))], + [0, Period((periods.YEAR, Instant((0, 1, 1)), 1))], + [1, Period((periods.YEAR, Instant((1, 1, 1)), 1))], + [999, Period((periods.YEAR, Instant((999, 1, 1)), 1))], + [1000, Period((periods.YEAR, Instant((1000, 1, 1)), 1))], + ["1000", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], + ["1000-1", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], + ["1000-1-1", Period((periods.DAY, Instant((1000, 1, 1)), 1))], + ["1000-01", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], + ["1000-01-01", Period((periods.DAY, Instant((1000, 1, 1)), 1))], + ["1004-02-29", Period((periods.DAY, Instant((1004, 2, 29)), 1))], + ["year:1000", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], + ["year:1000-01", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], + ["year:1000-01-01", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], + ["year:1000:1", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], + ["year:1000-01:1", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], + ["year:1000-01-01:1", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], + ["year:1000:3", Period((periods.YEAR, Instant((1000, 1, 1)), 3))], + ["year:1000-01:3", Period((periods.YEAR, Instant((1000, 1, 1)), 3))], + ["month:1000-01-01:3", Period((periods.MONTH, Instant((1000, 1, 1)), 3))], + ["month:1000-01", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], + ["month:1000-01-01", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], + ["month:1000-01:1", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], + ["month:1000-01:3", Period((periods.MONTH, Instant((1000, 1, 1)), 3))], + ["month:1000-01-01:3", Period((periods.MONTH, Instant((1000, 1, 1)), 3))], + ["month:1000-01-01:3", Period((periods.MONTH, Instant((1000, 1, 1)), 3))], + ["day:1000-01-01", Period((periods.DAY, Instant((1000, 1, 1)), 1))], + ["day:1000-01-01:3", Period((periods.DAY, Instant((1000, 1, 1)), 3))], + ]) +def test_period_with_a_valid_argument(arg, expected): + assert periods.period(arg) == expected + + +@pytest.mark.parametrize("arg, error", [ + [None, ValueError], + [periods.YEAR, ValueError], + [datetime.date(1, 1, 1), ValueError], + ["1000-0", ValueError], + ["1000-13", ValueError], + ["1000-0-0", ValueError], + ["1000-1-0", ValueError], + ["1000-2-31", ValueError], + ["1", ValueError], + ["a", ValueError], + ["year", ValueError], + ["999", ValueError], + ["1:1000", ValueError], + ["a:1000", ValueError], + ["month:1000", ValueError], + ["day:1000-01", ValueError], + ["1000:a", ValueError], + ["1000:1", ValueError], + ["1000-01:1", ValueError], + ["1000-01-01:1", ValueError], + ["month:1000:1", ValueError], + ["day:1000:1", ValueError], + ["day:1000-01:1", ValueError], + [(), ValueError], + [{}, ValueError], + ["", ValueError], + [(None,), ValueError], + [(None, None), ValueError], + [(None, None, None), ValueError], + [(None, None, None, None), ValueError], + [(datetime.date(1, 1, 1),), ValueError], + [(Instant((1, 1, 1)),), ValueError], + [(Period((periods.DAY, Instant((1, 1, 1)), 365)),), ValueError], + [(1,), ValueError], + [(1, 1), ValueError], + [(1, 1, 1), ValueError], + [(-1,), ValueError], + [(-1, -1), ValueError], + [(-1, -1, -1), ValueError], + [("-1",), ValueError], + [("-1", "-1"), ValueError], + [("-1", "-1", "-1"), ValueError], + [("1-1",), ValueError], + [("1-1-1",), ValueError], + ]) +def test_period_with_an_invalid_argument(arg, error): + with pytest.raises(error): + periods.period(arg) + + +@pytest.mark.parametrize("arg, expected", [ + ["1", None], + ["999", None], + ["1000", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], + ["1000-1", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], + ["1000-01", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], + ["1000-1-1", Period((periods.DAY, Instant((1000, 1, 1)), 1))], + ["1000-01-1", Period((periods.DAY, Instant((1000, 1, 1)), 1))], + ["1000-01-01", Period((periods.DAY, Instant((1000, 1, 1)), 1))], + ["1000-01-99", None], + ]) +def test__parse_simple_period_with_a_valid_argument(arg, expected): + assert helpers._parse_simple_period(arg) == expected + + +@pytest.mark.parametrize("arg, expected", [ + [Period((periods.DAY, Instant((1, 1, 1)), 365)), "100_365"], + [Period((periods.MONTH, Instant((1, 1, 1)), 12)), "200_12"], + [Period((periods.YEAR, Instant((1, 1, 1)), 2)), "300_2"], + [Period((periods.ETERNITY, Instant((1, 1, 1)), 1)), "400_1"], + [(periods.DAY, None, 1), "100_1"], + [(periods.MONTH, None, -1000), "200_-1000"], + ]) +def test_key_period_size_with_a_valid_argument(arg, expected): + assert periods.key_period_size(arg) == expected diff --git a/openfisca_core/periods/tests/test_period.py b/openfisca_core/periods/tests/test_period.py new file mode 100644 index 0000000000..4aab4d3339 --- /dev/null +++ b/openfisca_core/periods/tests/test_period.py @@ -0,0 +1,80 @@ +import pytest + +from openfisca_core import periods +from openfisca_core.periods import Instant, Period + + +@pytest.mark.parametrize("date_unit, instant, size, expected", [ + [periods.YEAR, Instant((2022, 1, 1)), 1, "2022"], + [periods.MONTH, Instant((2022, 1, 1)), 12, "2022"], + [periods.YEAR, Instant((2022, 3, 1)), 1, "year:2022-03"], + [periods.MONTH, Instant((2022, 3, 1)), 12, "year:2022-03"], + [periods.YEAR, Instant((2022, 1, 1)), 3, "year:2022:3"], + [periods.YEAR, Instant((2022, 1, 3)), 3, "year:2022:3"], + ]) +def test_str_with_years(date_unit, instant, size, expected): + assert str(Period((date_unit, instant, size))) == expected + + +@pytest.mark.parametrize("date_unit, instant, size, expected", [ + [periods.MONTH, Instant((2022, 1, 1)), 1, "2022-01"], + [periods.MONTH, Instant((2022, 1, 1)), 3, "month:2022-01:3"], + [periods.MONTH, Instant((2022, 3, 1)), 3, "month:2022-03:3"], + ]) +def test_str_with_months(date_unit, instant, size, expected): + assert str(Period((date_unit, instant, size))) == expected + + +@pytest.mark.parametrize("date_unit, instant, size, expected", [ + [periods.DAY, Instant((2022, 1, 1)), 1, "2022-01-01"], + [periods.DAY, Instant((2022, 1, 1)), 3, "day:2022-01-01:3"], + [periods.DAY, Instant((2022, 3, 1)), 3, "day:2022-03-01:3"], + ]) +def test_str_with_days(date_unit, instant, size, expected): + assert str(Period((date_unit, instant, size))) == expected + + +@pytest.mark.parametrize("period, unit, length, first, last", [ + (periods.period('year:2014:2'), periods.YEAR, 2, periods.period('2014'), periods.period('2015')), + (periods.period(2017), periods.MONTH, 12, periods.period('2017-01'), periods.period('2017-12')), + (periods.period('year:2014:2'), periods.MONTH, 24, periods.period('2014-01'), periods.period('2015-12')), + (periods.period('month:2014-03:3'), periods.MONTH, 3, periods.period('2014-03'), periods.period('2014-05')), + (periods.period(2017), periods.DAY, 365, periods.period('2017-01-01'), periods.period('2017-12-31')), + (periods.period('year:2014:2'), periods.DAY, 730, periods.period('2014-01-01'), periods.period('2015-12-31')), + (periods.period('month:2014-03:3'), periods.DAY, 92, periods.period('2014-03-01'), periods.period('2014-05-31')), + ]) +def test_subperiods(period, unit, length, first, last): + subperiods = period.get_subperiods(unit) + assert len(subperiods) == length + assert subperiods[0] == first + assert subperiods[-1] == last + + +@pytest.mark.parametrize("date_unit, instant, size, expected", [ + [periods.MONTH, Instant((2022, 12, 1)), 1, 1], + [periods.MONTH, Instant((2012, 2, 3)), 1, 1], + [periods.MONTH, Instant((2022, 1, 3)), 3, 3], + [periods.MONTH, Instant((2012, 1, 3)), 3, 3], + [periods.YEAR, Instant((2022, 12, 1)), 1, 12], + [periods.YEAR, Instant((2012, 1, 1)), 1, 12], + [periods.YEAR, Instant((2022, 1, 1)), 2, 24], + ]) +def test_day_size_in_months(date_unit, instant, size, expected): + period = Period((date_unit, instant, size)) + assert period.size_in_months == expected + + +@pytest.mark.parametrize("date_unit, instant, size, expected", [ + [periods.DAY, Instant((2022, 12, 31)), 1, 1], + [periods.DAY, Instant((2022, 12, 31)), 3, 3], + [periods.MONTH, Instant((2022, 12, 1)), 1, 31], + [periods.MONTH, Instant((2012, 2, 3)), 1, 29], + [periods.MONTH, Instant((2022, 1, 3)), 3, 31 + 28 + 31], + [periods.MONTH, Instant((2012, 1, 3)), 3, 31 + 29 + 31], + [periods.YEAR, Instant((2022, 12, 1)), 1, 365], + [periods.YEAR, Instant((2012, 1, 1)), 1, 366], + [periods.YEAR, Instant((2022, 1, 1)), 2, 730], + ]) +def test_day_size_in_days(date_unit, instant, size, expected): + period = Period((date_unit, instant, size)) + assert period.size_in_days == expected diff --git a/tests/core/test_periods.py b/tests/core/test_periods.py deleted file mode 100644 index 0816a0ce2e..0000000000 --- a/tests/core/test_periods.py +++ /dev/null @@ -1,26 +0,0 @@ -# -*- coding: utf-8 -*- - - -import pytest - -from openfisca_core.periods import YEAR, MONTH, DAY, period - - -@pytest.mark.parametrize("test", [ - (period('year:2014:2'), YEAR, 2, period('2014'), period('2015')), - (period(2017), MONTH, 12, period('2017-01'), period('2017-12')), - (period('year:2014:2'), MONTH, 24, period('2014-01'), period('2015-12')), - (period('month:2014-03:3'), MONTH, 3, period('2014-03'), period('2014-05')), - (period(2017), DAY, 365, period('2017-01-01'), period('2017-12-31')), - (period('year:2014:2'), DAY, 730, period('2014-01-01'), period('2015-12-31')), - (period('month:2014-03:3'), DAY, 92, period('2014-03-01'), period('2014-05-31')), - ]) -def test_subperiods(test): - - def check_subperiods(period, unit, length, first, last): - subperiods = period.get_subperiods(unit) - assert len(subperiods) == length - assert subperiods[0] == first - assert subperiods[-1] == last - - check_subperiods(*test) From fb6c119367a618b186829c5074ac31f0300994d3 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Mon, 1 Aug 2022 19:59:34 +0200 Subject: [PATCH 43/70] Revert "Fix flake8/pycodestyle dependency error" This reverts commit 7e4538aaf9034c8f6c008a6d9a7318b7c5e1d10d. --- setup.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index 68d0b0f029..7d10342eaf 100644 --- a/setup.py +++ b/setup.py @@ -38,18 +38,18 @@ ] api_requirements = [ - 'markupsafe >= 2.0.1, < 2.1.0', - 'flask >= 1.1.4, < 2.0.0', + 'markupsafe == 2.0.1', # While flask revision < 2 + 'flask == 1.1.4', 'flask-cors == 3.0.10', 'gunicorn >= 20.0.0, < 21.0.0', - 'werkzeug >= 1.0.1, < 2.0.0', + 'werkzeug >= 1.0.0, < 2.0.0', ] dev_requirements = [ 'autopep8 >= 1.4.0, < 1.6.0', 'coverage == 6.0.2', 'darglint == 1.8.0', - 'flake8 >= 3.9.0, < 4.0.0', + 'flake8 >= 4.0.0, < 4.1.0', 'flake8-bugbear >= 19.3.0, < 20.0.0', 'flake8-docstrings == 1.6.0', 'flake8-print >= 3.1.0, < 4.0.0', From 52561741e050f0faa5c80968c267a55e60220c85 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Tue, 2 Aug 2022 09:33:30 +0200 Subject: [PATCH 44/70] Rationalise Instant doc & tests --- openfisca_core/periods/instant_.py | 106 ++++++++++++------- openfisca_core/periods/tests/test_instant.py | 27 +++++ 2 files changed, 94 insertions(+), 39 deletions(-) create mode 100644 openfisca_core/periods/tests/test_instant.py diff --git a/openfisca_core/periods/instant_.py b/openfisca_core/periods/instant_.py index 63cc63636a..0b0ba56a94 100644 --- a/openfisca_core/periods/instant_.py +++ b/openfisca_core/periods/instant_.py @@ -11,35 +11,39 @@ class Instant(tuple): - """An instant in time (year, month, day). + """An instant in time (``year``, ``month``, ``day``). - An :class:`.Instant` represents the most atomic and indivisible - legislation's time unit. + An ``Instant`` represents the most atomic and indivisible + legislation's date unit. Current implementation considers this unit to be a day, so - :obj:`instants <.Instant>` can be thought of as "day dates". + ``instants`` can be thought of as "day dates". Args: - (tuple(tuple(int, int, int))): + (tuple(int, int, int)): The ``year``, ``month``, and ``day``, accordingly. Examples: >>> instant = Instant((2021, 9, 13)) - >>> repr(Instant) - "" + ``Instants`` are represented as a ``tuple`` containing the date units: >>> repr(instant) 'Instant((2021, 9, 13))' + However, their user-friendly representation is as a date in the + ISO format: + >>> str(instant) '2021-09-13' + Because ``Instants`` are ``tuples``, they are immutable, which allows + us to use them as keys in hashmaps: + >>> dict([(instant, (2021, 9, 13))]) {Instant((2021, 9, 13)): (2021, 9, 13)} - >>> list(instant) - [2021, 9, 13] + All the rest of the ``tuple`` protocols are inherited as well: >>> instant[0] 2021 @@ -53,33 +57,9 @@ class Instant(tuple): >>> instant == (2021, 9, 13) True - >>> instant != (2021, 9, 13) - False - >>> instant > (2020, 9, 13) True - >>> instant < (2020, 9, 13) - False - - >>> instant >= (2020, 9, 13) - True - - >>> instant <= (2020, 9, 13) - False - - >>> instant.year - 2021 - - >>> instant.month - 9 - - >>> instant.day - 13 - - >>> instant.date - datetime.date(2021, 9, 13) - >>> year, month, day = instant """ @@ -97,18 +77,66 @@ def __str__(self) -> str: @property def year(self) -> int: + """The ``year`` of the ``Instant``. + + Example: + >>> instant = Instant((2021, 10, 1)) + >>> instant.year + 2021 + + Returns: + An int. + + """ + return self[0] @property def month(self) -> int: + """The ``month`` of the ``Instant``. + + Example: + >>> instant = Instant((2021, 10, 1)) + >>> instant.month + 10 + + Returns: + An int. + + """ + return self[1] @property def day(self) -> int: + """The ``day`` of the ``Instant``. + + Example: + >>> instant = Instant((2021, 10, 1)) + >>> instant.day + 1 + + Returns: + An int. + + """ + return self[2] @property def date(self) -> datetime.date: + """The date representation of the ``Instant``. + + Example: + >>> instant = Instant((2021, 10, 1)) + >>> instant.date + datetime.date(2021, 10, 1) + + Returns: + A datetime.time. + + """ + instant_date = config.date_by_instant_cache.get(self) if instant_date is None: @@ -120,16 +148,16 @@ def offset(self, offset: Union[str, int], unit: str) -> types.Instant: """Increments/decrements the given instant with offset units. Args: - offset: How much of ``unit`` to offset. - unit: What to offset + offset (str | int): How much of ``unit`` to offset. + unit (str): What to offset. Returns: - :obj:`.Instant`: A new :obj:`.Instant` in time. + Instant: A new one. Raises: - :exc:`AssertionError`: When ``unit`` is not a date unit. - :exc:`AssertionError`: When ``offset`` is not either ``first-of``, - ``last-of``, or any :obj:`int`. + AssertionError: When ``unit`` is not a date unit. + AssertionError: When ``offset`` is not either ``first-of``, + ``last-of``, or any ``int``. Examples: >>> Instant((2020, 12, 31)).offset("first-of", "month") diff --git a/openfisca_core/periods/tests/test_instant.py b/openfisca_core/periods/tests/test_instant.py new file mode 100644 index 0000000000..5ad68cf88d --- /dev/null +++ b/openfisca_core/periods/tests/test_instant.py @@ -0,0 +1,27 @@ +import pytest + +from openfisca_core import periods +from openfisca_core.periods import Instant + + +@pytest.fixture +def instant(): + return Instant((2020, 2, 29)) + + +@pytest.mark.parametrize("offset, unit, expected", [ + ["first-of", periods.YEAR, Instant((2020, 1, 1))], + ["first-of", periods.MONTH, Instant((2020, 2, 1))], + ["first-of", periods.DAY, Instant((2020, 2, 29))], + ["last-of", periods.YEAR, Instant((2020, 12, 31))], + ["last-of", periods.MONTH, Instant((2020, 2, 29))], + ["last-of", periods.DAY, Instant((2020, 2, 29))], + [-3, periods.YEAR, Instant((2017, 2, 28))], + [-3, periods.MONTH, Instant((2019, 11, 29))], + [-3, periods.DAY, Instant((2020, 2, 26))], + [3, periods.YEAR, Instant((2023, 2, 28))], + [3, periods.MONTH, Instant((2020, 5, 29))], + [3, periods.DAY, Instant((2020, 3, 3))], + ]) +def test_offset(instant, offset, unit, expected): + assert expected == instant.offset(offset, unit) From 632566a4142393dadf4a62ca82ebc89c7de2d832 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Tue, 2 Aug 2022 11:51:27 +0200 Subject: [PATCH 45/70] Simplify periods' doc --- openfisca_core/periods/period_.py | 495 ++++++++----------- openfisca_core/periods/tests/test_helpers.py | 107 +--- openfisca_core/periods/tests/test_instant.py | 2 +- 3 files changed, 215 insertions(+), 389 deletions(-) diff --git a/openfisca_core/periods/period_.py b/openfisca_core/periods/period_.py index 20c301a8b7..4b8dc6437f 100644 --- a/openfisca_core/periods/period_.py +++ b/openfisca_core/periods/period_.py @@ -14,39 +14,44 @@ class Period(tuple): """Toolbox to handle date intervals. - A :class:`.Period` is a triple (``unit``, ``start``, ``size``). + A ``Period`` is a triple (``unit``, ``start``, ``size``). Attributes: - unit (:obj:`str`): + unit (str): Either ``year``, ``month``, ``day`` or ``eternity``. start (:obj:`.Instant`): The "instant" the :obj:`.Period` starts at. - size (:obj:`int`): + size (int): The amount of ``unit``, starting at ``start``, at least ``1``. Args: - (tuple(tuple(str, .Instant, int))): + tuple(str, .Instant, int))): The ``unit``, ``start``, and ``size``, accordingly. Examples: >>> instant = Instant((2021, 9, 1)) - >>> period = Period(("year", instant, 3)) + >>> period = Period((config.YEAR, instant, 3)) - >>> repr(Period) - "" + ``Periods`` are represented as a ``tuple`` containing the ``unit``, + an ``Instant`` and the ``size``: >>> repr(period) "Period(('year', Instant((2021, 9, 1)), 3))" + Their user-friendly representation is as a date in the + ISO format, prefixed with the ``unit`` and suffixed with its ``size``: + >>> str(period) 'year:2021-09:3' - >>> dict([period, instant]) + However, you won't be able to use them as hashmaps keys. Because they + contain a nested data structure, they're not hashable: + + >>> dict([period, (2021, 9, 13)]) Traceback (most recent call last): ValueError: dictionary update sequence element #0 has length 3... - >>> list(period) - ['year', Instant((2021, 9, 1)), 3] + All the rest of the ``tuple`` protocols are inherited as well: >>> period[0] 'year' @@ -60,69 +65,10 @@ class Period(tuple): >>> period == Period(("year", instant, 3)) True - >>> period != Period(("year", instant, 3)) - False - >>> period > Period(("year", instant, 3)) False - >>> period < Period(("year", instant, 3)) - False - - >>> period >= Period(("year", instant, 3)) - True - - >>> period <= Period(("year", instant, 3)) - True - - >>> period.date - Traceback (most recent call last): - AssertionError: "date" is undefined for a period of size > 1 - - >>> Period(("year", instant, 1)).date - datetime.date(2021, 9, 1) - - >>> period.days - 1096 - - >>> period.size - 3 - - >>> period.size_in_months - 36 - - >>> period.size_in_days - 1096 - - >>> period.start - Instant((2021, 9, 1)) - - >>> period.stop - Instant((2024, 8, 31)) - - >>> period.unit - 'year' - - >>> period.last_3_months - Period(('month', Instant((2021, 6, 1)), 3)) - - >>> period.last_month - Period(('month', Instant((2021, 8, 1)), 1)) - - >>> period.last_year - Period(('year', Instant((2020, 1, 1)), 1)) - - >>> period.n_2 - Period(('year', Instant((2019, 1, 1)), 1)) - - >>> period.this_year - Period(('year', Instant((2021, 1, 1)), 1)) - - >>> period.first_month - Period(('month', Instant((2021, 9, 1)), 1)) - - >>> period.first_day - Period(('day', Instant((2021, 9, 1)), 1)) + >>> unit, (year, month, day), size = period Since a period is a triple it can be used as a dictionary key. @@ -197,279 +143,164 @@ def __str__(self) -> str: @property def date(self) -> datetime.date: - assert self.size == 1, '"date" is undefined for a period of size > 1: {}'.format(self) - return self.start.date + """The date representation of the ``period``'s' start date. - @property - def days(self) -> int: - """Count the number of days in period.""" - return (self.stop.date - self.start.date).days + 1 - - def get_subperiods(self, unit: str) -> Sequence[types.Period]: - """Return the list of all the periods of unit ``unit``. + Returns: + A datetime.date. Examples: - >>> period = Period(("year", Instant((2021, 1, 1)), 1)) - >>> period.get_subperiods("month") - [Period(('month', Instant((2021, 1, 1)), 1)),...2021, 12, 1)), 1))] + >>> instant = Instant((2021, 10, 1)) + >>> period = Period((config.YEAR, instant, 1)) + >>> period.date + datetime.date(2021, 10, 1) - >>> period = Period(("year", Instant((2021, 1, 1)), 2)) - >>> period.get_subperiods("year") - [Period(('year', Instant((2021, 1, 1)), 1)),...((2022, 1, 1)), 1))] + >>> period = Period((config.YEAR, instant, 3)) + >>> period.date + Traceback (most recent call last): + ValueError: "date" is undefined for a period of size > 1: year:2021-09:3. """ - if helpers.unit_weight(self.unit) < helpers.unit_weight(unit): - raise ValueError('Cannot subdivide {0} into {1}'.format(self.unit, unit)) - - if unit == config.YEAR: - return [self.this_year.offset(i, config.YEAR) for i in range(self.size)] - - if unit == config.MONTH: - return [self.first_month.offset(i, config.MONTH) for i in range(self.size_in_months)] - - if unit == config.DAY: - return [self.first_day.offset(i, config.DAY) for i in range(self.size_in_days)] - - def offset( - self, - offset: Union[str, int], - unit: Optional[str] = None, - ) -> types.Period: - """Increment (or decrement) the given period with offset units. - - Examples: - >>> Period(("day", Instant((2021, 1, 1)), 365)).offset(1) - Period(('day', Instant((2021, 1, 2)), 365)) - - >>> Period(("day", Instant((2021, 1, 1)), 365)).offset(1, "day") - Period(('day', Instant((2021, 1, 2)), 365)) - - >>> Period(("day", Instant((2021, 1, 1)), 365)).offset(1, "month") - Period(('day', Instant((2021, 2, 1)), 365)) - - >>> Period(("day", Instant((2021, 1, 1)), 365)).offset(1, "year") - Period(('day', Instant((2022, 1, 1)), 365)) - - >>> Period(("month", Instant((2021, 1, 1)), 12)).offset(1) - Period(('month', Instant((2021, 2, 1)), 12)) - - >>> Period(("month", Instant((2021, 1, 1)), 12)).offset(1, "day") - Period(('month', Instant((2021, 1, 2)), 12)) - - >>> Period(("month", Instant((2021, 1, 1)), 12)).offset(1, "month") - Period(('month', Instant((2021, 2, 1)), 12)) - - >>> Period(("month", Instant((2021, 1, 1)), 12)).offset(1, "year") - Period(('month', Instant((2022, 1, 1)), 12)) - - >>> Period(("year", Instant((2021, 1, 1)), 1)).offset(1) - Period(('year', Instant((2022, 1, 1)), 1)) - - >>> Period(("year", Instant((2021, 1, 1)), 1)).offset(1, "day") - Period(('year', Instant((2021, 1, 2)), 1)) - - >>> Period(("year", Instant((2021, 1, 1)), 1)).offset(1, "month") - Period(('year', Instant((2021, 2, 1)), 1)) - - >>> Period(("year", Instant((2021, 1, 1)), 1)).offset(1, "year") - Period(('year', Instant((2022, 1, 1)), 1)) - - >>> Period(("day", Instant((2011, 2, 28)), 1)).offset(1) - Period(('day', Instant((2011, 3, 1)), 1)) - - >>> Period(("month", Instant((2011, 2, 28)), 1)).offset(1) - Period(('month', Instant((2011, 3, 28)), 1)) - - >>> Period(("year", Instant((2011, 2, 28)), 1)).offset(1) - Period(('year', Instant((2012, 2, 28)), 1)) - - >>> Period(("day", Instant((2011, 3, 1)), 1)).offset(-1) - Period(('day', Instant((2011, 2, 28)), 1)) - - >>> Period(("month", Instant((2011, 3, 1)), 1)).offset(-1) - Period(('month', Instant((2011, 2, 1)), 1)) - - >>> Period(("year", Instant((2011, 3, 1)), 1)).offset(-1) - Period(('year', Instant((2010, 3, 1)), 1)) - - >>> Period(("day", Instant((2014, 1, 30)), 1)).offset(3) - Period(('day', Instant((2014, 2, 2)), 1)) - - >>> Period(("month", Instant((2014, 1, 30)), 1)).offset(3) - Period(('month', Instant((2014, 4, 30)), 1)) - - >>> Period(("year", Instant((2014, 1, 30)), 1)).offset(3) - Period(('year', Instant((2017, 1, 30)), 1)) - - >>> Period(("day", Instant((2021, 1, 1)), 365)).offset(-3) - Period(('day', Instant((2020, 12, 29)), 365)) - - >>> Period(("month", Instant((2021, 1, 1)), 12)).offset(-3) - Period(('month', Instant((2020, 10, 1)), 12)) - - >>> Period(("year", Instant((2014, 1, 1)), 1)).offset(-3) - Period(('year', Instant((2011, 1, 1)), 1)) - - >>> Period(("day", Instant((2014, 2, 3)), 1)).offset("first-of", "month") - Period(('day', Instant((2014, 2, 1)), 1)) - - >>> Period(("day", Instant((2014, 2, 3)), 1)).offset("first-of", "year") - Period(('day', Instant((2014, 1, 1)), 1)) - - >>> Period(("day", Instant((2014, 2, 3)), 4)).offset("first-of", "month") - Period(('day', Instant((2014, 2, 1)), 4)) - - >>> Period(("day", Instant((2014, 2, 3)), 4)).offset("first-of", "year") - Period(('day', Instant((2014, 1, 1)), 4)) - - >>> Period(("month", Instant((2014, 2, 3)), 1)).offset("first-of") - Period(('month', Instant((2014, 2, 1)), 1)) - - >>> Period(("month", Instant((2014, 2, 3)), 1)).offset("first-of", "month") - Period(('month', Instant((2014, 2, 1)), 1)) - - >>> Period(("month", Instant((2014, 2, 3)), 1)).offset("first-of", "year") - Period(('month', Instant((2014, 1, 1)), 1)) - - >>> Period(("month", Instant((2014, 2, 3)), 4)).offset("first-of") - Period(('month', Instant((2014, 2, 1)), 4)) - - >>> Period(("month", Instant((2014, 2, 3)), 4)).offset("first-of", "month") - Period(('month', Instant((2014, 2, 1)), 4)) + if self.size != 1: + raise ValueError(f'"date" is undefined for a period of size > 1: {self}.') - >>> Period(("month", Instant((2014, 2, 3)), 4)).offset("first-of", "year") - Period(('month', Instant((2014, 1, 1)), 4)) - - >>> Period(("year", Instant((2014, 1, 30)), 1)).offset("first-of") - Period(('year', Instant((2014, 1, 1)), 1)) - - >>> Period(("year", Instant((2014, 1, 30)), 1)).offset("first-of", "month") - Period(('year', Instant((2014, 1, 1)), 1)) - - >>> Period(("year", Instant((2014, 1, 30)), 1)).offset("first-of", "year") - Period(('year', Instant((2014, 1, 1)), 1)) - - >>> Period(("year", Instant((2014, 2, 3)), 1)).offset("first-of") - Period(('year', Instant((2014, 1, 1)), 1)) - - >>> Period(("year", Instant((2014, 2, 3)), 1)).offset("first-of", "month") - Period(('year', Instant((2014, 2, 1)), 1)) - - >>> Period(("year", Instant((2014, 2, 3)), 1)).offset("first-of", "year") - Period(('year', Instant((2014, 1, 1)), 1)) - - >>> Period(("day", Instant((2014, 2, 3)), 1)).offset("last-of", "month") - Period(('day', Instant((2014, 2, 28)), 1)) - - >>> Period(("day", Instant((2014, 2, 3)), 1)).offset("last-of", "year") - Period(('day', Instant((2014, 12, 31)), 1)) + return self.start.date - >>> Period(("day", Instant((2014, 2, 3)), 4)).offset("last-of", "month") - Period(('day', Instant((2014, 2, 28)), 4)) + @property + def unit(self) -> str: + """The ``unit`` of the ``Period``. - >>> Period(("day", Instant((2014, 2, 3)), 4)).offset("last-of", "year") - Period(('day', Instant((2014, 12, 31)), 4)) + Returns: + An int. - >>> Period(("month", Instant((2014, 2, 3)), 1)).offset("last-of") - Period(('month', Instant((2014, 2, 28)), 1)) + Example: + >>> instant = Instant((2021, 10, 1)) + >>> period = Period((config.YEAR, instant, 3)) + >>> period.unit + 'year' - >>> Period(("month", Instant((2014, 2, 3)), 1)).offset("last-of", "month") - Period(('month', Instant((2014, 2, 28)), 1)) + """ - >>> Period(("month", Instant((2014, 2, 3)), 1)).offset("last-of", "year") - Period(('month', Instant((2014, 12, 31)), 1)) + return self[0] - >>> Period(("month", Instant((2014, 2, 3)), 4)).offset("last-of") - Period(('month', Instant((2014, 2, 28)), 4)) + @property + def days(self) -> int: + """Count the number of days in period. - >>> Period(("month", Instant((2014, 2, 3)), 4)).offset("last-of", "month") - Period(('month', Instant((2014, 2, 28)), 4)) + Returns: + An int. - >>> Period(("month", Instant((2014, 2, 3)), 4)).offset("last-of", "year") - Period(('month', Instant((2014, 12, 31)), 4)) + Examples: + >>> instant = Instant((2021, 10, 1)) + >>> period = Period((config.YEAR, instant, 3)) + >>> period.size_in_days + 1096 - >>> Period(("year", Instant((2014, 2, 3)), 1)).offset("last-of") - Period(('year', Instant((2014, 12, 31)), 1)) + >>> period = Period((config.MONTH, instant, 3)) + >>> period.size_in_days + 92 - >>> Period(("year", Instant((2014, 1, 1)), 1)).offset("last-of", "month") - Period(('year', Instant((2014, 1, 31)), 1)) + """ - >>> Period(("year", Instant((2014, 2, 3)), 1)).offset("last-of", "year") - Period(('year', Instant((2014, 12, 31)), 1)) + return (self.stop.date - self.start.date).days + 1 - >>> Period(("year", Instant((2014, 2, 3)), 1)).offset("last-of") - Period(('year', Instant((2014, 12, 31)), 1)) + @property + def size(self) -> int: + """The ``size`` of the ``Period``. - >>> Period(("year", Instant((2014, 2, 3)), 1)).offset("last-of", "month") - Period(('year', Instant((2014, 2, 28)), 1)) + Returns: + An int. - >>> Period(("year", Instant((2014, 2, 3)), 1)).offset("last-of", "year") - Period(('year', Instant((2014, 12, 31)), 1)) + Example: + >>> instant = Instant((2021, 10, 1)) + >>> period = Period((config.YEAR, instant, 3)) + >>> period.size + 3 """ - return self.__class__((self[0], self[1].offset(offset, self[0] if unit is None else unit), self[2])) - - def contains(self, other: types.Period) -> bool: - """Returns ``True`` if the period contains ``other``. - - For instance, ``period(2015)`` contains ``period(2015-01)``. + return self[2] - """ + @property + def size_in_months(self) -> int: + """The ``size`` of the ``Period`` in months. - return self.start <= other.start and self.stop >= other.stop + Returns: + An int. - @property - def size(self) -> int: - """Return the size of the period.""" + Examples: + >>> instant = Instant((2021, 10, 1)) + >>> period = Period((config.YEAR, instant, 3)) + >>> period.size_in_months + 36 - return self[2] + >>> period = Period((config.DAY, instant, 3)) + >>> period.size_in_months + Traceback (most recent call last): + ValueError: Cannot calculate number of months in day. - @property - def size_in_months(self) -> int: - """Return the size of the period in months.""" + """ if (self[0] == config.MONTH): return self[2] + if(self[0] == config.YEAR): return self[2] * 12 - raise ValueError("Cannot calculate number of months in {0}".format(self[0])) + + raise ValueError(f"Cannot calculate number of months in {self[0]}") @property def size_in_days(self) -> int: - """Return the size of the period in days.""" + """The ``size`` of the ``Period`` in days. + + Examples: + >>> instant = Instant((2019, 10, 1)) + >>> period = Period((config.YEAR, instant, 3)) + >>> period.size_in_days + 1096 + + >>> period = Period((config.MONTH, instant, 3)) + >>> period.size_in_days + 92 + + """ unit, instant, length = self if unit == config.DAY: return length + if unit in [config.MONTH, config.YEAR]: last_day = self.start.offset(length, unit).offset(-1, config.DAY) return (last_day.date - self.start.date).days + 1 - raise ValueError("Cannot calculate number of days in {0}".format(unit)) + raise ValueError(f"Cannot calculate number of days in {unit}") @property def start(self) -> types.Instant: - """Return the first day of the period as an Instant instance.""" + """The ``Instant`` at which the ``Period`` starts. + + Returns: + An Instant. + + Example: + >>> instant = Instant((2021, 10, 1)) + >>> period = Period((config.YEAR, instant, 3)) + >>> period.start + Instant((2021, 10, 1)) + + """ return self[1] @property def stop(self) -> types.Instant: - """Return the last day of the period as an Instant instance. - - Examples: - >>> Period(("year", Instant((2022, 1, 1)), 1)).stop - Instant((2022, 12, 31)) - - >>> Period(("month", Instant((2022, 1, 1)), 12)).stop - Instant((2022, 12, 31)) + """Last day of the ``Period`` as an ``Instant``. - >>> Period(("day", Instant((2022, 1, 1)), 365)).stop - Instant((2022, 12, 31)) + Returns: + An Instant. + Examples: >>> Period(("year", Instant((2012, 2, 29)), 1)).stop Instant((2013, 2, 28)) @@ -479,15 +310,6 @@ def stop(self) -> types.Instant: >>> Period(("day", Instant((2012, 2, 29)), 1)).stop Instant((2012, 2, 29)) - >>> Period(("year", Instant((2012, 2, 29)), 2)).stop - Instant((2014, 2, 28)) - - >>> Period(("month", Instant((2012, 2, 29)), 2)).stop - Instant((2012, 4, 28)) - - >>> Period(("day", Instant((2012, 2, 29)), 2)).stop - Instant((2012, 3, 1)) - """ unit, start_instant, size = self @@ -531,12 +353,6 @@ def stop(self) -> types.Instant: day -= month_last_day return Instant((year, month, day)) - @property - def unit(self) -> str: - return self[0] - - # Reference periods - @property def last_month(self) -> types.Period: return self.first_month.offset(-1) @@ -569,3 +385,80 @@ def first_month(self) -> types.Period: @property def first_day(self) -> types.Period: return self.__class__((config.DAY, self.start, 1)) + + def get_subperiods(self, unit: str) -> Sequence[types.Period]: + """Return the list of all the periods of unit ``unit``. + + Examples: + >>> period = Period((config.YEAR, Instant((2021, 1, 1)), 1)) + >>> period.get_subperiods(config.MONTH) + [Period(('month', Instant((2021, 1, 1)), 1)),...2021, 12, 1)), 1))] + + >>> period = Period((config.YEAR, Instant((2021, 1, 1)), 2)) + >>> period.get_subperiods(config.YEAR) + [Period(('year', Instant((2021, 1, 1)), 1)),...((2022, 1, 1)), 1))] + + """ + + if helpers.unit_weight(self.unit) < helpers.unit_weight(unit): + raise ValueError('Cannot subdivide {0} into {1}'.format(self.unit, unit)) + + if unit == config.YEAR: + return [self.this_year.offset(i, config.YEAR) for i in range(self.size)] + + if unit == config.MONTH: + return [self.first_month.offset(i, config.MONTH) for i in range(self.size_in_months)] + + if unit == config.DAY: + return [self.first_day.offset(i, config.DAY) for i in range(self.size_in_days)] + + def offset( + self, + offset: Union[str, int], + unit: Optional[str] = None, + ) -> types.Period: + """Increment (or decrement) the given period with offset units. + + Args: + offset (str | int): How much of ``unit`` to offset. + unit (str): What to offset. + + Returns: + Period: A new one. + + Examples: + >>> Period(("day", Instant((2014, 2, 3)), 1)).offset("first-of", "month") + Period(('day', Instant((2014, 2, 1)), 1)) + + >>> Period(("month", Instant((2014, 2, 3)), 4)).offset("last-of", "month") + Period(('month', Instant((2014, 2, 28)), 4)) + + >>> Period(("day", Instant((2021, 1, 1)), 365)).offset(-3) + Period(('day', Instant((2020, 12, 29)), 365)) + + >>> Period(("day", Instant((2021, 1, 1)), 365)).offset(1, "year") + Period(('day', Instant((2022, 1, 1)), 365)) + + """ + + return self.__class__((self[0], self[1].offset(offset, self[0] if unit is None else unit), self[2])) + + def contains(self, other: types.Period) -> bool: + """Checks if a ``period`` contains another one. + + Args: + other (:obj:`.Period`): The other ``Period``. + + Returns + True if ``other`` is contained, otherwise False. + + Example: + >>> period = Period((config.YEAR, Instant((2021, 1, 1)), 1)) + >>> sub_period = Period((config.MONTH, Instant((2021, 1, 1)), 3)) + + >>> period.contains(sub_period) + True + + """ + + return self.start <= other.start and self.stop >= other.stop diff --git a/openfisca_core/periods/tests/test_helpers.py b/openfisca_core/periods/tests/test_helpers.py index 6dd09788b9..01bb02c416 100644 --- a/openfisca_core/periods/tests/test_helpers.py +++ b/openfisca_core/periods/tests/test_helpers.py @@ -11,30 +11,12 @@ [datetime.date(1, 1, 1), Instant((1, 1, 1))], [Instant((1, 1, 1)), Instant((1, 1, 1))], [Period((periods.DAY, Instant((1, 1, 1)), 365)), Instant((1, 1, 1))], - [-1, Instant((-1, 1, 1))], - [0, Instant((0, 1, 1))], - [1, Instant((1, 1, 1))], - [999, Instant((999, 1, 1))], [1000, Instant((1000, 1, 1))], ["1000", Instant((1000, 1, 1))], ["1000-01", Instant((1000, 1, 1))], ["1000-01-01", Instant((1000, 1, 1))], - [(None,), Instant((None, 1, 1))], - [(None, None), Instant((None, None, 1))], - [(None, None, None), Instant((None, None, None))], - [(datetime.date(1, 1, 1),), Instant((datetime.date(1, 1, 1), 1, 1))], - [(Instant((1, 1, 1)),), Instant((Instant((1, 1, 1)), 1, 1))], - [(Period((periods.DAY, Instant((1, 1, 1)), 365)),), Instant((Period((periods.DAY, Instant((1, 1, 1)), 365)), 1, 1))], - [(-1,), Instant((-1, 1, 1))], - [(-1, -1), Instant((-1, -1, 1))], - [(-1, -1, -1), Instant((-1, -1, -1))], - [("-1",), Instant(("-1", 1, 1))], - [("-1", "-1"), Instant(("-1", "-1", 1))], - [("-1", "-1", "-1"), Instant(("-1", "-1", "-1"))], - [("1-1",), Instant(("1-1", 1, 1))], - [("1-1-1",), Instant(("1-1-1", 1, 1))], ]) -def test_instant_with_a_valid_argument(arg, expected): +def test_instant(arg, expected): assert periods.instant(arg) == expected @@ -42,25 +24,18 @@ def test_instant_with_a_valid_argument(arg, expected): [periods.YEAR, ValueError], [periods.ETERNITY, ValueError], ["1000-0", ValueError], - ["1000-0-0", ValueError], ["1000-1", ValueError], + ["1000-13", ValueError], + ["1000-0-0", ValueError], ["1000-1-1", ValueError], - ["1", ValueError], - ["a", ValueError], - ["year", ValueError], - ["eternity", ValueError], - ["999", ValueError], - ["1:1000-01-01", ValueError], - ["a:1000-01-01", ValueError], + ["1000-01-0", ValueError], + ["1000-01-1", ValueError], + ["1000-01-32", ValueError], + ["month:1000", ValueError], + ["month:1000:1", ValueError], ["year:1000-01-01", ValueError], ["year:1000-01-01:1", ValueError], ["year:1000-01-01:3", ValueError], - ["1000-01-01:a", ValueError], - ["1000-01-01:1", ValueError], - [(), AssertionError], - [{}, AssertionError], - ["", ValueError], - [(None, None, None, None), AssertionError], ]) def test_instant_with_an_invalid_argument(arg, error): with pytest.raises(error): @@ -73,20 +48,17 @@ def test_instant_with_an_invalid_argument(arg, error): [Instant((4, 2, 29)), datetime.date(4, 2, 29)], [(1, 1, 1), datetime.date(1, 1, 1)], ]) -def test_instant_date_with_a_valid_argument(arg, expected): +def test_instant_date(arg, expected): assert periods.instant_date(arg) == expected @pytest.mark.parametrize("arg, error", [ [Instant((-1, 1, 1)), ValueError], [Instant((1, -1, 1)), ValueError], - [Instant((1, 13, -1)), ValueError], [Instant((1, 1, -1)), ValueError], + [Instant((1, 13, 1)), ValueError], [Instant((1, 1, 32)), ValueError], [Instant((1, 2, 29)), ValueError], - [Instant(("1", 1, 1)), TypeError], - [(1,), TypeError], - [(1, 1), TypeError], ]) def test_instant_date_with_an_invalid_argument(arg, error): with pytest.raises(error): @@ -99,10 +71,6 @@ def test_instant_date_with_an_invalid_argument(arg, error): [periods.ETERNITY, Period((periods.ETERNITY, Instant((1, 1, 1)), float("inf")))], [Instant((1, 1, 1)), Period((periods.DAY, Instant((1, 1, 1)), 1))], [Period((periods.DAY, Instant((1, 1, 1)), 365)), Period((periods.DAY, Instant((1, 1, 1)), 365))], - [-1, Period((periods.YEAR, Instant((-1, 1, 1)), 1))], - [0, Period((periods.YEAR, Instant((0, 1, 1)), 1))], - [1, Period((periods.YEAR, Instant((1, 1, 1)), 1))], - [999, Period((periods.YEAR, Instant((999, 1, 1)), 1))], [1000, Period((periods.YEAR, Instant((1000, 1, 1)), 1))], ["1000", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], ["1000-1", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], @@ -111,24 +79,19 @@ def test_instant_date_with_an_invalid_argument(arg, error): ["1000-01-01", Period((periods.DAY, Instant((1000, 1, 1)), 1))], ["1004-02-29", Period((periods.DAY, Instant((1004, 2, 29)), 1))], ["year:1000", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], - ["year:1000-01", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], - ["year:1000-01-01", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], - ["year:1000:1", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], - ["year:1000-01:1", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], - ["year:1000-01-01:1", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], ["year:1000:3", Period((periods.YEAR, Instant((1000, 1, 1)), 3))], + ["year:1000-01", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], ["year:1000-01:3", Period((periods.YEAR, Instant((1000, 1, 1)), 3))], - ["month:1000-01-01:3", Period((periods.MONTH, Instant((1000, 1, 1)), 3))], + ["year:1000-01-01", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], + ["year:1000-01-01:3", Period((periods.YEAR, Instant((1000, 1, 1)), 3))], ["month:1000-01", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], - ["month:1000-01-01", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], - ["month:1000-01:1", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], ["month:1000-01:3", Period((periods.MONTH, Instant((1000, 1, 1)), 3))], - ["month:1000-01-01:3", Period((periods.MONTH, Instant((1000, 1, 1)), 3))], + ["month:1000-01-01", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], ["month:1000-01-01:3", Period((periods.MONTH, Instant((1000, 1, 1)), 3))], ["day:1000-01-01", Period((periods.DAY, Instant((1000, 1, 1)), 1))], ["day:1000-01-01:3", Period((periods.DAY, Instant((1000, 1, 1)), 3))], ]) -def test_period_with_a_valid_argument(arg, expected): +def test_period(arg, expected): assert periods.period(arg) == expected @@ -136,47 +99,19 @@ def test_period_with_a_valid_argument(arg, expected): [None, ValueError], [periods.YEAR, ValueError], [datetime.date(1, 1, 1), ValueError], + ["1000:1", ValueError], ["1000-0", ValueError], ["1000-13", ValueError], + ["1000-01:1", ValueError], ["1000-0-0", ValueError], ["1000-1-0", ValueError], ["1000-2-31", ValueError], - ["1", ValueError], - ["a", ValueError], - ["year", ValueError], - ["999", ValueError], - ["1:1000", ValueError], - ["a:1000", ValueError], - ["month:1000", ValueError], - ["day:1000-01", ValueError], - ["1000:a", ValueError], - ["1000:1", ValueError], - ["1000-01:1", ValueError], ["1000-01-01:1", ValueError], + ["month:1000", ValueError], ["month:1000:1", ValueError], ["day:1000:1", ValueError], + ["day:1000-01", ValueError], ["day:1000-01:1", ValueError], - [(), ValueError], - [{}, ValueError], - ["", ValueError], - [(None,), ValueError], - [(None, None), ValueError], - [(None, None, None), ValueError], - [(None, None, None, None), ValueError], - [(datetime.date(1, 1, 1),), ValueError], - [(Instant((1, 1, 1)),), ValueError], - [(Period((periods.DAY, Instant((1, 1, 1)), 365)),), ValueError], - [(1,), ValueError], - [(1, 1), ValueError], - [(1, 1, 1), ValueError], - [(-1,), ValueError], - [(-1, -1), ValueError], - [(-1, -1, -1), ValueError], - [("-1",), ValueError], - [("-1", "-1"), ValueError], - [("-1", "-1", "-1"), ValueError], - [("1-1",), ValueError], - [("1-1-1",), ValueError], ]) def test_period_with_an_invalid_argument(arg, error): with pytest.raises(error): @@ -194,7 +129,7 @@ def test_period_with_an_invalid_argument(arg, error): ["1000-01-01", Period((periods.DAY, Instant((1000, 1, 1)), 1))], ["1000-01-99", None], ]) -def test__parse_simple_period_with_a_valid_argument(arg, expected): +def test__parse_simple_period(arg, expected): assert helpers._parse_simple_period(arg) == expected @@ -203,8 +138,6 @@ def test__parse_simple_period_with_a_valid_argument(arg, expected): [Period((periods.MONTH, Instant((1, 1, 1)), 12)), "200_12"], [Period((periods.YEAR, Instant((1, 1, 1)), 2)), "300_2"], [Period((periods.ETERNITY, Instant((1, 1, 1)), 1)), "400_1"], - [(periods.DAY, None, 1), "100_1"], - [(periods.MONTH, None, -1000), "200_-1000"], ]) def test_key_period_size_with_a_valid_argument(arg, expected): assert periods.key_period_size(arg) == expected diff --git a/openfisca_core/periods/tests/test_instant.py b/openfisca_core/periods/tests/test_instant.py index 5ad68cf88d..31db048c93 100644 --- a/openfisca_core/periods/tests/test_instant.py +++ b/openfisca_core/periods/tests/test_instant.py @@ -24,4 +24,4 @@ def instant(): [3, periods.DAY, Instant((2020, 3, 3))], ]) def test_offset(instant, offset, unit, expected): - assert expected == instant.offset(offset, unit) + assert instant.offset(offset, unit) == expected From 45b1eb2248bfef70b45f1b9e06df874e64f8b9af Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Tue, 2 Aug 2022 12:21:29 +0200 Subject: [PATCH 46/70] Rationalise period's offset tests --- openfisca_core/periods/instant_.py | 2 +- openfisca_core/periods/period_.py | 4 ++ openfisca_core/periods/tests/test_period.py | 57 ++++++++++++++++----- openfisca_tasks/lint.mk | 2 + openfisca_tasks/test_code.mk | 1 + 5 files changed, 53 insertions(+), 13 deletions(-) diff --git a/openfisca_core/periods/instant_.py b/openfisca_core/periods/instant_.py index 0b0ba56a94..7c40a056b8 100644 --- a/openfisca_core/periods/instant_.py +++ b/openfisca_core/periods/instant_.py @@ -7,6 +7,7 @@ from openfisca_core import types +from .. import periods from . import config @@ -75,7 +76,6 @@ def __str__(self) -> str: return instant_str - @property def year(self) -> int: """The ``year`` of the ``Instant``. diff --git a/openfisca_core/periods/period_.py b/openfisca_core/periods/period_.py index 4b8dc6437f..a118c347f8 100644 --- a/openfisca_core/periods/period_.py +++ b/openfisca_core/periods/period_.py @@ -314,8 +314,10 @@ def stop(self) -> types.Instant: unit, start_instant, size = self year, month, day = start_instant + if unit == config.ETERNITY: return Instant((float("inf"), float("inf"), float("inf"))) + if unit == 'day': if size > 1: day += size - 1 @@ -327,6 +329,7 @@ def stop(self) -> types.Instant: month = 1 day -= month_last_day month_last_day = calendar.monthrange(year, month)[1] + else: if unit == 'month': month += size @@ -351,6 +354,7 @@ def stop(self) -> types.Instant: year += 1 month = 1 day -= month_last_day + return Instant((year, month, day)) @property diff --git a/openfisca_core/periods/tests/test_period.py b/openfisca_core/periods/tests/test_period.py index 4aab4d3339..16d20733b2 100644 --- a/openfisca_core/periods/tests/test_period.py +++ b/openfisca_core/periods/tests/test_period.py @@ -4,6 +4,11 @@ from openfisca_core.periods import Instant, Period +@pytest.fixture +def instant(): + return Instant((2022, 12, 31)) + + @pytest.mark.parametrize("date_unit, instant, size, expected", [ [periods.YEAR, Instant((2022, 1, 1)), 1, "2022"], [periods.MONTH, Instant((2022, 1, 1)), 12, "2022"], @@ -34,20 +39,48 @@ def test_str_with_days(date_unit, instant, size, expected): assert str(Period((date_unit, instant, size))) == expected -@pytest.mark.parametrize("period, unit, length, first, last", [ - (periods.period('year:2014:2'), periods.YEAR, 2, periods.period('2014'), periods.period('2015')), - (periods.period(2017), periods.MONTH, 12, periods.period('2017-01'), periods.period('2017-12')), - (periods.period('year:2014:2'), periods.MONTH, 24, periods.period('2014-01'), periods.period('2015-12')), - (periods.period('month:2014-03:3'), periods.MONTH, 3, periods.period('2014-03'), periods.period('2014-05')), - (periods.period(2017), periods.DAY, 365, periods.period('2017-01-01'), periods.period('2017-12-31')), - (periods.period('year:2014:2'), periods.DAY, 730, periods.period('2014-01-01'), periods.period('2015-12-31')), - (periods.period('month:2014-03:3'), periods.DAY, 92, periods.period('2014-03-01'), periods.period('2014-05-31')), +@pytest.mark.parametrize("period_unit, unit, start, cease, count", [ + [periods.YEAR, periods.YEAR, Instant((2022, 1, 1)), Instant((2024, 1, 1)), 3], + [periods.YEAR, periods.MONTH, Instant((2022, 12, 1)), Instant((2025, 11, 1)), 36], + [periods.YEAR, periods.DAY, Instant((2022, 12, 31)), Instant((2025, 12, 30)), 1096], + [periods.MONTH, periods.MONTH, Instant((2022, 12, 1)), Instant((2023, 2, 1)), 3], + [periods.MONTH, periods.DAY, Instant((2022, 12, 31)), Instant((2023, 3, 30)), 90], + [periods.DAY, periods.DAY, Instant((2022, 12, 31)), Instant((2023, 1, 2)), 3], ]) -def test_subperiods(period, unit, length, first, last): +def test_subperiods(instant, period_unit, unit, start, cease, count): + period = Period((period_unit, instant, 3)) subperiods = period.get_subperiods(unit) - assert len(subperiods) == length - assert subperiods[0] == first - assert subperiods[-1] == last + assert len(subperiods) == count + assert subperiods[0] == Period((unit, start, 1)) + assert subperiods[-1] == Period((unit, cease, 1)) + + +@pytest.mark.parametrize("period_unit, offset, unit, expected", [ + [periods.YEAR, "first-of", periods.YEAR, Period(('year', Instant((2022, 1, 1)), 3))], + [periods.YEAR, "first-of", periods.MONTH, Period(('year', Instant((2022, 12, 1)), 3))], + [periods.YEAR, "last-of", periods.YEAR, Period(('year', Instant((2022, 12, 31)), 3))], + [periods.YEAR, "last-of", periods.MONTH, Period(('year', Instant((2022, 12, 31)), 3))], + [periods.YEAR, -3, periods.YEAR, Period(('year', Instant((2019, 12, 31)), 3))], + [periods.YEAR, 1, periods.MONTH, Period(('year', Instant((2023, 1, 31)), 3))], + [periods.YEAR, 3, periods.DAY, Period(('year', Instant((2023, 1, 3)), 3))], + [periods.MONTH, "first-of", periods.YEAR, Period(('month', Instant((2022, 1, 1)), 3))], + [periods.MONTH, "first-of", periods.MONTH, Period(('month', Instant((2022, 12, 1)), 3))], + [periods.MONTH, "last-of", periods.YEAR, Period(('month', Instant((2022, 12, 31)), 3))], + [periods.MONTH, "last-of", periods.MONTH, Period(('month', Instant((2022, 12, 31)), 3))], + [periods.MONTH, -3, periods.YEAR, Period(('month', Instant((2019, 12, 31)), 3))], + [periods.MONTH, 1, periods.MONTH, Period(('month', Instant((2023, 1, 31)), 3))], + [periods.MONTH, 3, periods.DAY, Period(('month', Instant((2023, 1, 3)), 3))], + [periods.DAY, "first-of", periods.YEAR, Period(('day', Instant((2022, 1, 1)), 3))], + [periods.DAY, "first-of", periods.MONTH, Period(('day', Instant((2022, 12, 1)), 3))], + [periods.DAY, "last-of", periods.YEAR, Period(('day', Instant((2022, 12, 31)), 3))], + [periods.DAY, "last-of", periods.MONTH, Period(('day', Instant((2022, 12, 31)), 3))], + [periods.DAY, -3, periods.YEAR, Period(('day', Instant((2019, 12, 31)), 3))], + [periods.DAY, 1, periods.MONTH, Period(('day', Instant((2023, 1, 31)), 3))], + [periods.DAY, 3, periods.DAY, Period(('day', Instant((2023, 1, 3)), 3))], + ]) +def test_offset(instant, period_unit, offset, unit, expected): + period = Period((period_unit, instant, 3)) + assert period.offset(offset, unit) == expected @pytest.mark.parametrize("date_unit, instant, size, expected", [ diff --git a/openfisca_tasks/lint.mk b/openfisca_tasks/lint.mk index 115c6267bb..7d546e0937 100644 --- a/openfisca_tasks/lint.mk +++ b/openfisca_tasks/lint.mk @@ -17,6 +17,7 @@ check-style: $(shell git ls-files "*.py") ## Run linters to check for syntax and style errors in the doc. lint-doc: \ lint-doc-commons \ + lint-doc-periods \ lint-doc-types \ ; @@ -42,6 +43,7 @@ check-types: ## Run static type checkers for type errors (strict). lint-typing-strict: \ lint-typing-strict-commons \ + lint-typing-strict-periods \ lint-typing-strict-types \ ; diff --git a/openfisca_tasks/test_code.mk b/openfisca_tasks/test_code.mk index 63fdd4386a..c60c294bf7 100644 --- a/openfisca_tasks/test_code.mk +++ b/openfisca_tasks/test_code.mk @@ -34,6 +34,7 @@ test-core: $(shell pytest --quiet --quiet --collect-only 2> /dev/null | cut -f 1 @pytest --quiet --capture=no --xdoctest --xdoctest-verbose=0 \ openfisca_core/commons \ openfisca_core/holders \ + openfisca_core/periods \ openfisca_core/types @PYTEST_ADDOPTS="$${PYTEST_ADDOPTS} ${pytest_args}" \ coverage run -m \ From 01c1cd0b0168d16655df971683e1892d49c85b82 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Thu, 28 Jul 2022 21:52:03 +0200 Subject: [PATCH 47/70] Version & CHANGELOG bump --- CHANGELOG.md | 10 ++++++++++ openfisca_core/periods/helpers.py | 24 ++++++++++++++++++------ openfisca_core/periods/instant_.py | 23 ++++++++++++++++++++++- openfisca_core/periods/period_.py | 4 ++-- setup.py | 2 +- 5 files changed, 53 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 601cdab211..bfde1fd9da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +# 39.0.0 [#1138](https://github.com/openfisca/openfisca-core/pull/1138) + +#### Breaking changes + +- Deprecate `periods.intersect`. + +#### Technical changes + +- Fix `openfisca_core.periods` doctests. + # 38.0.0 [#989](https://github.com/openfisca/openfisca-core/pull/989) #### New Features diff --git a/openfisca_core/periods/helpers.py b/openfisca_core/periods/helpers.py index 994e126b40..6c575d2989 100644 --- a/openfisca_core/periods/helpers.py +++ b/openfisca_core/periods/helpers.py @@ -54,7 +54,10 @@ def instant(value: Any) -> Optional[types.Instant]: if isinstance(value, str): if not config.INSTANT_PATTERN.match(value): - raise ValueError(f"'{value}' is not a valid instant. Instants are described using the 'YYYY-MM-DD' format, for instance '2015-06-15'.") + raise ValueError( + f"'{value}' is not a valid instant. Instants are described" + "using the 'YYYY-MM-DD' format, for instance '2015-06-15'." + ) instant = Instant( int(fragment) @@ -186,7 +189,7 @@ def period(value: Any) -> types.Period: if ":" not in value: _raise_error(value) - components = value.split(':') + components = value.split(":") # Left-most component must be a valid unit unit = components[0] @@ -242,18 +245,24 @@ def _parse_simple_period(value: str) -> Optional[types.Period]: try: date = datetime.datetime.strptime(value, '%Y') + except ValueError: try: date = datetime.datetime.strptime(value, '%Y-%m') + except ValueError: try: date = datetime.datetime.strptime(value, '%Y-%m-%d') + except ValueError: return None + else: return Period((config.DAY, Instant((date.year, date.month, date.day)), 1)) + else: return Period((config.MONTH, Instant((date.year, date.month, 1)), 1)) + else: return Period((config.YEAR, Instant((date.year, date.month, 1)), 1)) @@ -264,15 +273,18 @@ def _raise_error(value: str) -> NoReturn: Examples: >>> _raise_error("Oi mate!") Traceback (most recent call last): - ValueError: Expected a period (eg. '2017', '2017-01', '2017-01-01', ...); got: 'Oi mate!'. + ValueError: Expected a period (eg. '2017', '2017-01', '2017-01-01', ...); got: + 'Oi mate!'. Learn more about legal period formats in OpenFisca: + . """ message = os.linesep.join([ - "Expected a period (eg. '2017', '2017-01', '2017-01-01', ...); got: '{}'.".format(value), - "Learn more about legal period formats in OpenFisca:", + "Expected a period (eg. '2017', '2017-01', '2017-01-01', ...); got:", + f"'{value}'. Learn more about legal period formats in OpenFisca:", "." ]) + raise ValueError(message) @@ -302,7 +314,7 @@ def key_period_size(period: types.Period) -> str: unit, start, size = period - return '{}_{}'.format(unit_weight(unit), size) + return f"{unit_weight(unit)}_{size}" def unit_weights() -> Dict[str, int]: diff --git a/openfisca_core/periods/instant_.py b/openfisca_core/periods/instant_.py index 7c40a056b8..dbb37678fe 100644 --- a/openfisca_core/periods/instant_.py +++ b/openfisca_core/periods/instant_.py @@ -7,7 +7,6 @@ from openfisca_core import types -from .. import periods from . import config @@ -76,6 +75,7 @@ def __str__(self) -> str: return instant_str + @property def year(self) -> int: """The ``year`` of the ``Instant``. @@ -175,56 +175,77 @@ def offset(self, offset: Union[str, int], unit: str) -> types.Instant: """ year, month, day = self + assert unit in (config.DAY, config.MONTH, config.YEAR), 'Invalid unit: {} of type {}'.format(unit, type(unit)) + if offset == 'first-of': if unit == config.MONTH: day = 1 + elif unit == config.YEAR: month = 1 day = 1 + elif offset == 'last-of': if unit == config.MONTH: day = calendar.monthrange(year, month)[1] + elif unit == config.YEAR: month = 12 day = 31 + else: assert isinstance(offset, int), 'Invalid offset: {} of type {}'.format(offset, type(offset)) + if unit == config.DAY: day += offset + if offset < 0: while day < 1: month -= 1 + if month == 0: year -= 1 month = 12 + day += calendar.monthrange(year, month)[1] + elif offset > 0: month_last_day = calendar.monthrange(year, month)[1] + while day > month_last_day: month += 1 + if month == 13: year += 1 month = 1 + day -= month_last_day month_last_day = calendar.monthrange(year, month)[1] + elif unit == config.MONTH: month += offset + if offset < 0: while month < 1: year -= 1 month += 12 + elif offset > 0: while month > 12: year += 1 month -= 12 month_last_day = calendar.monthrange(year, month)[1] + if day > month_last_day: day = month_last_day + elif unit == config.YEAR: year += offset + # Handle february month of leap year. month_last_day = calendar.monthrange(year, month)[1] + if day > month_last_day: day = month_last_day diff --git a/openfisca_core/periods/period_.py b/openfisca_core/periods/period_.py index a118c347f8..40fe8ce91b 100644 --- a/openfisca_core/periods/period_.py +++ b/openfisca_core/periods/period_.py @@ -157,7 +157,7 @@ def date(self) -> datetime.date: >>> period = Period((config.YEAR, instant, 3)) >>> period.date Traceback (most recent call last): - ValueError: "date" is undefined for a period of size > 1: year:2021-09:3. + ValueError: "date" is undefined for a period of size > 1: year:2021-10:3. """ @@ -247,7 +247,7 @@ def size_in_months(self) -> int: if(self[0] == config.YEAR): return self[2] * 12 - raise ValueError(f"Cannot calculate number of months in {self[0]}") + raise ValueError(f"Cannot calculate number of months in {self[0]}.") @property def size_in_days(self) -> int: diff --git a/setup.py b/setup.py index 7d10342eaf..2355296ea0 100644 --- a/setup.py +++ b/setup.py @@ -63,7 +63,7 @@ setup( name = 'OpenFisca-Core', - version = '38.0.0', + version = '39.0.0', author = 'OpenFisca Team', author_email = 'contact@openfisca.org', classifiers = [ From c5b2b1fcc2072f3401ff8c69b907826145d3b653 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Wed, 14 Dec 2022 01:28:20 +0100 Subject: [PATCH 48/70] Reorder package --- openfisca_core/periods/__init__.py | 61 +++--- .../periods/{config.py => _config.py} | 0 .../periods/{helpers.py => _funcs.py} | 193 +++++++++--------- openfisca_core/periods/instant_.py | 26 +-- openfisca_core/periods/period_.py | 96 ++++----- openfisca_core/periods/tests/test_helpers.py | 103 +++++----- openfisca_core/periods/tests/test_instant.py | 27 ++- openfisca_core/periods/tests/test_period.py | 131 ++++++------ 8 files changed, 320 insertions(+), 317 deletions(-) rename openfisca_core/periods/{config.py => _config.py} (100%) rename openfisca_core/periods/{helpers.py => _funcs.py} (87%) diff --git a/openfisca_core/periods/__init__.py b/openfisca_core/periods/__init__.py index 8acddd62c9..7c229e4e79 100644 --- a/openfisca_core/periods/__init__.py +++ b/openfisca_core/periods/__init__.py @@ -1,27 +1,33 @@ -# 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 +"""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 ( # noqa: F401 DAY, MONTH, YEAR, @@ -32,13 +38,14 @@ year_or_month_or_day_re, ) -from .helpers import ( # noqa: F401 +from ._funcs import ( # noqa: F401 instant, instant_date, - period, key_period_size, - unit_weights, + parse_simple_period, + period, unit_weight, + unit_weights, ) from .instant_ import Instant # noqa: F401 diff --git a/openfisca_core/periods/config.py b/openfisca_core/periods/_config.py similarity index 100% rename from openfisca_core/periods/config.py rename to openfisca_core/periods/_config.py diff --git a/openfisca_core/periods/helpers.py b/openfisca_core/periods/_funcs.py similarity index 87% rename from openfisca_core/periods/helpers.py rename to openfisca_core/periods/_funcs.py index 6c575d2989..5299829bd3 100644 --- a/openfisca_core/periods/helpers.py +++ b/openfisca_core/periods/_funcs.py @@ -7,7 +7,7 @@ from openfisca_core import types -from . import config +from . import _config from .instant_ import Instant from .period_ import Period @@ -53,7 +53,7 @@ def instant(value: Any) -> Optional[types.Instant]: return value if isinstance(value, str): - if not config.INSTANT_PATTERN.match(value): + if not _config.INSTANT_PATTERN.match(value): raise ValueError( f"'{value}' is not a valid instant. Instants are described" "using the 'YYYY-MM-DD' format, for instance '2015-06-15'." @@ -111,14 +111,83 @@ def instant_date(instant: Optional[types.Instant]) -> Optional[datetime.date]: if instant is None: return None - instant_date = config.date_by_instant_cache.get(instant) + instant_date = _config.date_by_instant_cache.get(instant) if instant_date is None: - config.date_by_instant_cache[instant] = instant_date = datetime.date(*instant) + _config.date_by_instant_cache[instant] = instant_date = datetime.date(*instant) return instant_date +def key_period_size(period: types.Period) -> str: + """Define a key in order to sort periods by length. + + It uses two aspects: first, ``unit``, then, ``size``. + + Args: + period: An :mod:`.openfisca_core` :obj:`.Period`. + + Returns: + :obj:`str`: A string. + + Examples: + >>> instant = Instant((2021, 9, 14)) + + >>> period = Period(("day", instant, 1)) + >>> key_period_size(period) + '100_1' + + >>> period = Period(("year", instant, 3)) + >>> key_period_size(period) + '300_3' + + """ + + unit, start, size = period + + return f"{unit_weight(unit)}_{size}" + + +def parse_simple_period(value: str) -> Optional[types.Period]: + """Parse simple periods respecting the ISO format. + + Such as "2012" or "2015-03". + + Examples: + >>> parse_simple_period("2022") + Period(('year', Instant((2022, 1, 1)), 1)) + + >>> parse_simple_period("2022-02") + Period(('month', Instant((2022, 2, 1)), 1)) + + >>> parse_simple_period("2022-02-13") + Period(('day', Instant((2022, 2, 13)), 1)) + + """ + + try: + date = datetime.datetime.strptime(value, '%Y') + + except ValueError: + try: + date = datetime.datetime.strptime(value, '%Y-%m') + + except ValueError: + try: + date = datetime.datetime.strptime(value, '%Y-%m-%d') + + except ValueError: + return None + + else: + return Period((_config.DAY, Instant((date.year, date.month, date.day)), 1)) + + else: + return Period((_config.MONTH, Instant((date.year, date.month, 1)), 1)) + + else: + return Period((_config.YEAR, Instant((date.year, date.month, 1)), 1)) + def period(value: Any) -> types.Period: """Build a new period, aka a triple (unit, start_instant, size). @@ -168,19 +237,19 @@ def period(value: Any) -> types.Period: return value if isinstance(value, Instant): - return Period((config.DAY, value, 1)) + return Period((_config.DAY, value, 1)) - if value == "ETERNITY" or value == config.ETERNITY: + if value == "ETERNITY" or value == _config.ETERNITY: return Period(("eternity", instant(datetime.date.min), float("inf"))) if isinstance(value, int): - return Period((config.YEAR, Instant((value, 1, 1)), 1)) + return Period((_config.YEAR, Instant((value, 1, 1)), 1)) if not isinstance(value, str): _raise_error(value) # Try to parse as a simple period - period = _parse_simple_period(value) + period = parse_simple_period(value) if period is not None: return period @@ -194,11 +263,11 @@ def period(value: Any) -> types.Period: # Left-most component must be a valid unit unit = components[0] - if unit not in (config.DAY, config.MONTH, config.YEAR): + if unit not in (_config.DAY, _config.MONTH, _config.YEAR): _raise_error(value) # Middle component must be a valid iso period - base_period = _parse_simple_period(components[1]) + base_period = parse_simple_period(components[1]) if not base_period: _raise_error(value) @@ -226,45 +295,33 @@ def period(value: Any) -> types.Period: return Period((unit, base_period.start, size)) -def _parse_simple_period(value: str) -> Optional[types.Period]: - """Parse simple periods respecting the ISO format. - - Such as "2012" or "2015-03". +def unit_weights() -> Dict[str, int]: + """Assign weights to date units. Examples: - >>> _parse_simple_period("2022") - Period(('year', Instant((2022, 1, 1)), 1)) - - >>> _parse_simple_period("2022-02") - Period(('month', Instant((2022, 2, 1)), 1)) - - >>> _parse_simple_period("2022-02-13") - Period(('day', Instant((2022, 2, 13)), 1)) + >>> unit_weights() + {'day': 100, ...} """ - try: - date = datetime.datetime.strptime(value, '%Y') - - except ValueError: - try: - date = datetime.datetime.strptime(value, '%Y-%m') + return { + _config.DAY: 100, + _config.MONTH: 200, + _config.YEAR: 300, + _config.ETERNITY: 400, + } - except ValueError: - try: - date = datetime.datetime.strptime(value, '%Y-%m-%d') - except ValueError: - return None +def unit_weight(unit: str) -> int: + """Retrieves a specific date unit weight. - else: - return Period((config.DAY, Instant((date.year, date.month, date.day)), 1)) + Examples: + >>> unit_weight("day") + 100 - else: - return Period((config.MONTH, Instant((date.year, date.month, 1)), 1)) + """ - else: - return Period((config.YEAR, Instant((date.year, date.month, 1)), 1)) + return unit_weights()[unit] def _raise_error(value: str) -> NoReturn: @@ -286,61 +343,3 @@ def _raise_error(value: str) -> NoReturn: ]) raise ValueError(message) - - -def key_period_size(period: types.Period) -> str: - """Define a key in order to sort periods by length. - - It uses two aspects: first, ``unit``, then, ``size``. - - Args: - period: An :mod:`.openfisca_core` :obj:`.Period`. - - Returns: - :obj:`str`: A string. - - Examples: - >>> instant = Instant((2021, 9, 14)) - - >>> period = Period(("day", instant, 1)) - >>> key_period_size(period) - '100_1' - - >>> period = Period(("year", instant, 3)) - >>> key_period_size(period) - '300_3' - - """ - - unit, start, size = period - - return f"{unit_weight(unit)}_{size}" - - -def unit_weights() -> Dict[str, int]: - """Assign weights to date units. - - Examples: - >>> unit_weights() - {'day': 100, ...} - - """ - - return { - config.DAY: 100, - config.MONTH: 200, - config.YEAR: 300, - config.ETERNITY: 400, - } - - -def unit_weight(unit: str) -> int: - """Retrieves a specific date unit weight. - - Examples: - >>> unit_weight("day") - 100 - - """ - - return unit_weights()[unit] diff --git a/openfisca_core/periods/instant_.py b/openfisca_core/periods/instant_.py index dbb37678fe..0a1f677006 100644 --- a/openfisca_core/periods/instant_.py +++ b/openfisca_core/periods/instant_.py @@ -7,7 +7,7 @@ from openfisca_core import types -from . import config +from . import _config class Instant(tuple): @@ -68,10 +68,10 @@ def __repr__(self) -> str: return f"{self.__class__.__name__}({super(Instant, self).__repr__()})" def __str__(self) -> str: - instant_str = config.str_by_instant_cache.get(self) + instant_str = _config.str_by_instant_cache.get(self) if instant_str is None: - config.str_by_instant_cache[self] = instant_str = self.date.isoformat() + _config.str_by_instant_cache[self] = instant_str = self.date.isoformat() return instant_str @@ -137,10 +137,10 @@ def date(self) -> datetime.date: """ - instant_date = config.date_by_instant_cache.get(self) + instant_date = _config.date_by_instant_cache.get(self) if instant_date is None: - config.date_by_instant_cache[self] = instant_date = datetime.date(*self) + _config.date_by_instant_cache[self] = instant_date = datetime.date(*self) return instant_date @@ -176,28 +176,28 @@ def offset(self, offset: Union[str, int], unit: str) -> types.Instant: year, month, day = self - assert unit in (config.DAY, config.MONTH, config.YEAR), 'Invalid unit: {} of type {}'.format(unit, type(unit)) + assert unit in (_config.DAY, _config.MONTH, _config.YEAR), 'Invalid unit: {} of type {}'.format(unit, type(unit)) if offset == 'first-of': - if unit == config.MONTH: + if unit == _config.MONTH: day = 1 - elif unit == config.YEAR: + elif unit == _config.YEAR: month = 1 day = 1 elif offset == 'last-of': - if unit == config.MONTH: + if unit == _config.MONTH: day = calendar.monthrange(year, month)[1] - elif unit == config.YEAR: + elif unit == _config.YEAR: month = 12 day = 31 else: assert isinstance(offset, int), 'Invalid offset: {} of type {}'.format(offset, type(offset)) - if unit == config.DAY: + if unit == _config.DAY: day += offset if offset < 0: @@ -223,7 +223,7 @@ def offset(self, offset: Union[str, int], unit: str) -> types.Instant: day -= month_last_day month_last_day = calendar.monthrange(year, month)[1] - elif unit == config.MONTH: + elif unit == _config.MONTH: month += offset if offset < 0: @@ -240,7 +240,7 @@ def offset(self, offset: Union[str, int], unit: str) -> types.Instant: if day > month_last_day: day = month_last_day - elif unit == config.YEAR: + elif unit == _config.YEAR: year += offset # Handle february month of leap year. diff --git a/openfisca_core/periods/period_.py b/openfisca_core/periods/period_.py index 40fe8ce91b..a858f0abe8 100644 --- a/openfisca_core/periods/period_.py +++ b/openfisca_core/periods/period_.py @@ -7,7 +7,7 @@ from openfisca_core import types -from . import config, helpers +from . import _config, _funcs from .instant_ import Instant @@ -30,7 +30,7 @@ class Period(tuple): Examples: >>> instant = Instant((2021, 9, 1)) - >>> period = Period((config.YEAR, instant, 3)) + >>> period = Period((_config.YEAR, instant, 3)) ``Periods`` are represented as a ``tuple`` containing the ``unit``, an ``Instant`` and the ``size``: @@ -112,27 +112,27 @@ def __str__(self) -> str: unit, start_instant, size = self - if unit == config.ETERNITY: + if unit == _config.ETERNITY: return "ETERNITY" year, month, day = start_instant # 1 year long period - if (unit == config.MONTH and size == 12 or unit == config.YEAR and size == 1): + if (unit == _config.MONTH and size == 12 or unit == _config.YEAR and size == 1): if month == 1: # civil year starting from january return str(year) else: # rolling year - return '{}:{}-{:02d}'.format(config.YEAR, year, month) + return '{}:{}-{:02d}'.format(_config.YEAR, year, month) # simple month - if unit == config.MONTH and size == 1: + if unit == _config.MONTH and size == 1: return '{}-{:02d}'.format(year, month) # several civil years - if unit == config.YEAR and month == 1: + if unit == _config.YEAR and month == 1: return '{}:{}:{}'.format(unit, year, size) - if unit == config.DAY: + if unit == _config.DAY: if size == 1: return '{}-{:02d}-{:02d}'.format(year, month, day) else: @@ -150,11 +150,11 @@ def date(self) -> datetime.date: Examples: >>> instant = Instant((2021, 10, 1)) - >>> period = Period((config.YEAR, instant, 1)) + >>> period = Period((_config.YEAR, instant, 1)) >>> period.date datetime.date(2021, 10, 1) - >>> period = Period((config.YEAR, instant, 3)) + >>> period = Period((_config.YEAR, instant, 3)) >>> period.date Traceback (most recent call last): ValueError: "date" is undefined for a period of size > 1: year:2021-10:3. @@ -175,7 +175,7 @@ def unit(self) -> str: Example: >>> instant = Instant((2021, 10, 1)) - >>> period = Period((config.YEAR, instant, 3)) + >>> period = Period((_config.YEAR, instant, 3)) >>> period.unit 'year' @@ -192,11 +192,11 @@ def days(self) -> int: Examples: >>> instant = Instant((2021, 10, 1)) - >>> period = Period((config.YEAR, instant, 3)) + >>> period = Period((_config.YEAR, instant, 3)) >>> period.size_in_days 1096 - >>> period = Period((config.MONTH, instant, 3)) + >>> period = Period((_config.MONTH, instant, 3)) >>> period.size_in_days 92 @@ -213,7 +213,7 @@ def size(self) -> int: Example: >>> instant = Instant((2021, 10, 1)) - >>> period = Period((config.YEAR, instant, 3)) + >>> period = Period((_config.YEAR, instant, 3)) >>> period.size 3 @@ -230,21 +230,21 @@ def size_in_months(self) -> int: Examples: >>> instant = Instant((2021, 10, 1)) - >>> period = Period((config.YEAR, instant, 3)) + >>> period = Period((_config.YEAR, instant, 3)) >>> period.size_in_months 36 - >>> period = Period((config.DAY, instant, 3)) + >>> period = Period((_config.DAY, instant, 3)) >>> period.size_in_months Traceback (most recent call last): ValueError: Cannot calculate number of months in day. """ - if (self[0] == config.MONTH): + if (self[0] == _config.MONTH): return self[2] - if(self[0] == config.YEAR): + if(self[0] == _config.YEAR): return self[2] * 12 raise ValueError(f"Cannot calculate number of months in {self[0]}.") @@ -255,11 +255,11 @@ def size_in_days(self) -> int: Examples: >>> instant = Instant((2019, 10, 1)) - >>> period = Period((config.YEAR, instant, 3)) + >>> period = Period((_config.YEAR, instant, 3)) >>> period.size_in_days 1096 - >>> period = Period((config.MONTH, instant, 3)) + >>> period = Period((_config.MONTH, instant, 3)) >>> period.size_in_days 92 @@ -267,11 +267,11 @@ def size_in_days(self) -> int: unit, instant, length = self - if unit == config.DAY: + if unit == _config.DAY: return length - if unit in [config.MONTH, config.YEAR]: - last_day = self.start.offset(length, unit).offset(-1, config.DAY) + if unit in [_config.MONTH, _config.YEAR]: + last_day = self.start.offset(length, unit).offset(-1, _config.DAY) return (last_day.date - self.start.date).days + 1 raise ValueError(f"Cannot calculate number of days in {unit}") @@ -285,7 +285,7 @@ def start(self) -> types.Instant: Example: >>> instant = Instant((2021, 10, 1)) - >>> period = Period((config.YEAR, instant, 3)) + >>> period = Period((_config.YEAR, instant, 3)) >>> period.start Instant((2021, 10, 1)) @@ -315,7 +315,7 @@ def stop(self) -> types.Instant: unit, start_instant, size = self year, month, day = start_instant - if unit == config.ETERNITY: + if unit == _config.ETERNITY: return Instant((float("inf"), float("inf"), float("inf"))) if unit == 'day': @@ -364,57 +364,57 @@ def last_month(self) -> types.Period: @property def last_3_months(self) -> types.Period: start: types.Instant = self.first_month.start - return self.__class__((config.MONTH, start, 3)).offset(-3) + return self.__class__((_config.MONTH, start, 3)).offset(-3) @property def last_year(self) -> types.Period: - start: types.Instant = self.start.offset("first-of", config.YEAR) - return self.__class__((config.YEAR, start, 1)).offset(-1) + start: types.Instant = self.start.offset("first-of", _config.YEAR) + return self.__class__((_config.YEAR, start, 1)).offset(-1) @property def n_2(self) -> types.Period: - start: types.Instant = self.start.offset("first-of", config.YEAR) - return self.__class__((config.YEAR, start, 1)).offset(-2) + start: types.Instant = self.start.offset("first-of", _config.YEAR) + return self.__class__((_config.YEAR, start, 1)).offset(-2) @property def this_year(self) -> types.Period: - start: types.Instant = self.start.offset("first-of", config.YEAR) - return self.__class__((config.YEAR, start, 1)) + start: types.Instant = self.start.offset("first-of", _config.YEAR) + return self.__class__((_config.YEAR, start, 1)) @property def first_month(self) -> types.Period: - start: types.Instant = self.start.offset("first-of", config.MONTH) - return self.__class__((config.MONTH, start, 1)) + start: types.Instant = self.start.offset("first-of", _config.MONTH) + return self.__class__((_config.MONTH, start, 1)) @property def first_day(self) -> types.Period: - return self.__class__((config.DAY, self.start, 1)) + return self.__class__((_config.DAY, self.start, 1)) def get_subperiods(self, unit: str) -> Sequence[types.Period]: """Return the list of all the periods of unit ``unit``. Examples: - >>> period = Period((config.YEAR, Instant((2021, 1, 1)), 1)) - >>> period.get_subperiods(config.MONTH) + >>> period = Period((_config.YEAR, Instant((2021, 1, 1)), 1)) + >>> period.get_subperiods(_config.MONTH) [Period(('month', Instant((2021, 1, 1)), 1)),...2021, 12, 1)), 1))] - >>> period = Period((config.YEAR, Instant((2021, 1, 1)), 2)) - >>> period.get_subperiods(config.YEAR) + >>> period = Period((_config.YEAR, Instant((2021, 1, 1)), 2)) + >>> period.get_subperiods(_config.YEAR) [Period(('year', Instant((2021, 1, 1)), 1)),...((2022, 1, 1)), 1))] """ - if helpers.unit_weight(self.unit) < helpers.unit_weight(unit): + if _funcs.unit_weight(self.unit) < _funcs.unit_weight(unit): raise ValueError('Cannot subdivide {0} into {1}'.format(self.unit, unit)) - if unit == config.YEAR: - return [self.this_year.offset(i, config.YEAR) for i in range(self.size)] + if unit == _config.YEAR: + return [self.this_year.offset(i, _config.YEAR) for i in range(self.size)] - if unit == config.MONTH: - return [self.first_month.offset(i, config.MONTH) for i in range(self.size_in_months)] + if unit == _config.MONTH: + return [self.first_month.offset(i, _config.MONTH) for i in range(self.size_in_months)] - if unit == config.DAY: - return [self.first_day.offset(i, config.DAY) for i in range(self.size_in_days)] + if unit == _config.DAY: + return [self.first_day.offset(i, _config.DAY) for i in range(self.size_in_days)] def offset( self, @@ -457,8 +457,8 @@ def contains(self, other: types.Period) -> bool: True if ``other`` is contained, otherwise False. Example: - >>> period = Period((config.YEAR, Instant((2021, 1, 1)), 1)) - >>> sub_period = Period((config.MONTH, Instant((2021, 1, 1)), 3)) + >>> period = Period((_config.YEAR, Instant((2021, 1, 1)), 1)) + >>> sub_period = Period((_config.MONTH, Instant((2021, 1, 1)), 3)) >>> period.contains(sub_period) True diff --git a/openfisca_core/periods/tests/test_helpers.py b/openfisca_core/periods/tests/test_helpers.py index 01bb02c416..149b0a82fa 100644 --- a/openfisca_core/periods/tests/test_helpers.py +++ b/openfisca_core/periods/tests/test_helpers.py @@ -3,18 +3,17 @@ import pytest from openfisca_core import periods -from openfisca_core.periods import Instant, Period, helpers @pytest.mark.parametrize("arg, expected", [ [None, None], - [datetime.date(1, 1, 1), Instant((1, 1, 1))], - [Instant((1, 1, 1)), Instant((1, 1, 1))], - [Period((periods.DAY, Instant((1, 1, 1)), 365)), Instant((1, 1, 1))], - [1000, Instant((1000, 1, 1))], - ["1000", Instant((1000, 1, 1))], - ["1000-01", Instant((1000, 1, 1))], - ["1000-01-01", Instant((1000, 1, 1))], + [datetime.date(1, 1, 1), periods.Instant((1, 1, 1))], + [periods.Instant((1, 1, 1)), periods.Instant((1, 1, 1))], + [periods.Period((periods.DAY, periods.Instant((1, 1, 1)), 365)), periods.Instant((1, 1, 1))], + [1000, periods.Instant((1000, 1, 1))], + ["1000", periods.Instant((1000, 1, 1))], + ["1000-01", periods.Instant((1000, 1, 1))], + ["1000-01-01", periods.Instant((1000, 1, 1))], ]) def test_instant(arg, expected): assert periods.instant(arg) == expected @@ -44,8 +43,8 @@ def test_instant_with_an_invalid_argument(arg, error): @pytest.mark.parametrize("arg, expected", [ [None, None], - [Instant((1, 1, 1)), datetime.date(1, 1, 1)], - [Instant((4, 2, 29)), datetime.date(4, 2, 29)], + [periods.Instant((1, 1, 1)), datetime.date(1, 1, 1)], + [periods.Instant((4, 2, 29)), datetime.date(4, 2, 29)], [(1, 1, 1), datetime.date(1, 1, 1)], ]) def test_instant_date(arg, expected): @@ -53,12 +52,12 @@ def test_instant_date(arg, expected): @pytest.mark.parametrize("arg, error", [ - [Instant((-1, 1, 1)), ValueError], - [Instant((1, -1, 1)), ValueError], - [Instant((1, 1, -1)), ValueError], - [Instant((1, 13, 1)), ValueError], - [Instant((1, 1, 32)), ValueError], - [Instant((1, 2, 29)), ValueError], + [periods.Instant((-1, 1, 1)), ValueError], + [periods.Instant((1, -1, 1)), ValueError], + [periods.Instant((1, 1, -1)), ValueError], + [periods.Instant((1, 13, 1)), ValueError], + [periods.Instant((1, 1, 32)), ValueError], + [periods.Instant((1, 2, 29)), ValueError], ]) def test_instant_date_with_an_invalid_argument(arg, error): with pytest.raises(error): @@ -66,30 +65,30 @@ def test_instant_date_with_an_invalid_argument(arg, error): @pytest.mark.parametrize("arg, expected", [ - ["eternity", Period((periods.ETERNITY, Instant((1, 1, 1)), float("inf")))], - ["ETERNITY", Period((periods.ETERNITY, Instant((1, 1, 1)), float("inf")))], - [periods.ETERNITY, Period((periods.ETERNITY, Instant((1, 1, 1)), float("inf")))], - [Instant((1, 1, 1)), Period((periods.DAY, Instant((1, 1, 1)), 1))], - [Period((periods.DAY, Instant((1, 1, 1)), 365)), Period((periods.DAY, Instant((1, 1, 1)), 365))], - [1000, Period((periods.YEAR, Instant((1000, 1, 1)), 1))], - ["1000", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], - ["1000-1", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], - ["1000-1-1", Period((periods.DAY, Instant((1000, 1, 1)), 1))], - ["1000-01", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], - ["1000-01-01", Period((periods.DAY, Instant((1000, 1, 1)), 1))], - ["1004-02-29", Period((periods.DAY, Instant((1004, 2, 29)), 1))], - ["year:1000", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], - ["year:1000:3", Period((periods.YEAR, Instant((1000, 1, 1)), 3))], - ["year:1000-01", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], - ["year:1000-01:3", Period((periods.YEAR, Instant((1000, 1, 1)), 3))], - ["year:1000-01-01", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], - ["year:1000-01-01:3", Period((periods.YEAR, Instant((1000, 1, 1)), 3))], - ["month:1000-01", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], - ["month:1000-01:3", Period((periods.MONTH, Instant((1000, 1, 1)), 3))], - ["month:1000-01-01", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], - ["month:1000-01-01:3", Period((periods.MONTH, Instant((1000, 1, 1)), 3))], - ["day:1000-01-01", Period((periods.DAY, Instant((1000, 1, 1)), 1))], - ["day:1000-01-01:3", Period((periods.DAY, Instant((1000, 1, 1)), 3))], + ["eternity", periods.Period((periods.ETERNITY, periods.Instant((1, 1, 1)), float("inf")))], + ["ETERNITY", periods.Period((periods.ETERNITY, periods.Instant((1, 1, 1)), float("inf")))], + [periods.ETERNITY, periods.Period((periods.ETERNITY, periods.Instant((1, 1, 1)), float("inf")))], + [periods.Instant((1, 1, 1)), periods.Period((periods.DAY, periods.Instant((1, 1, 1)), 1))], + [periods.Period((periods.DAY, periods.Instant((1, 1, 1)), 365)), periods.Period((periods.DAY, periods.Instant((1, 1, 1)), 365))], + [1000, periods.Period((periods.YEAR, periods.Instant((1000, 1, 1)), 1))], + ["1000", periods.Period((periods.YEAR, periods.Instant((1000, 1, 1)), 1))], + ["1000-1", periods.Period((periods.MONTH, periods.Instant((1000, 1, 1)), 1))], + ["1000-1-1", periods.Period((periods.DAY, periods.Instant((1000, 1, 1)), 1))], + ["1000-01", periods.Period((periods.MONTH, periods.Instant((1000, 1, 1)), 1))], + ["1000-01-01", periods.Period((periods.DAY, periods.Instant((1000, 1, 1)), 1))], + ["1004-02-29", periods.Period((periods.DAY, periods.Instant((1004, 2, 29)), 1))], + ["year:1000", periods.Period((periods.YEAR, periods.Instant((1000, 1, 1)), 1))], + ["year:1000:3", periods.Period((periods.YEAR, periods.Instant((1000, 1, 1)), 3))], + ["year:1000-01", periods.Period((periods.YEAR, periods.Instant((1000, 1, 1)), 1))], + ["year:1000-01:3", periods.Period((periods.YEAR, periods.Instant((1000, 1, 1)), 3))], + ["year:1000-01-01", periods.Period((periods.YEAR, periods.Instant((1000, 1, 1)), 1))], + ["year:1000-01-01:3", periods.Period((periods.YEAR, periods.Instant((1000, 1, 1)), 3))], + ["month:1000-01", periods.Period((periods.MONTH, periods.Instant((1000, 1, 1)), 1))], + ["month:1000-01:3", periods.Period((periods.MONTH, periods.Instant((1000, 1, 1)), 3))], + ["month:1000-01-01", periods.Period((periods.MONTH, periods.Instant((1000, 1, 1)), 1))], + ["month:1000-01-01:3", periods.Period((periods.MONTH, periods.Instant((1000, 1, 1)), 3))], + ["day:1000-01-01", periods.Period((periods.DAY, periods.Instant((1000, 1, 1)), 1))], + ["day:1000-01-01:3", periods.Period((periods.DAY, periods.Instant((1000, 1, 1)), 3))], ]) def test_period(arg, expected): assert periods.period(arg) == expected @@ -121,23 +120,23 @@ def test_period_with_an_invalid_argument(arg, error): @pytest.mark.parametrize("arg, expected", [ ["1", None], ["999", None], - ["1000", Period((periods.YEAR, Instant((1000, 1, 1)), 1))], - ["1000-1", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], - ["1000-01", Period((periods.MONTH, Instant((1000, 1, 1)), 1))], - ["1000-1-1", Period((periods.DAY, Instant((1000, 1, 1)), 1))], - ["1000-01-1", Period((periods.DAY, Instant((1000, 1, 1)), 1))], - ["1000-01-01", Period((periods.DAY, Instant((1000, 1, 1)), 1))], + ["1000", periods.Period((periods.YEAR, periods.Instant((1000, 1, 1)), 1))], + ["1000-1", periods.Period((periods.MONTH, periods.Instant((1000, 1, 1)), 1))], + ["1000-01", periods.Period((periods.MONTH, periods.Instant((1000, 1, 1)), 1))], + ["1000-1-1", periods.Period((periods.DAY, periods.Instant((1000, 1, 1)), 1))], + ["1000-01-1", periods.Period((periods.DAY, periods.Instant((1000, 1, 1)), 1))], + ["1000-01-01", periods.Period((periods.DAY, periods.Instant((1000, 1, 1)), 1))], ["1000-01-99", None], ]) -def test__parse_simple_period(arg, expected): - assert helpers._parse_simple_period(arg) == expected +def testparse_simple_period(arg, expected): + assert periods.parse_simple_period(arg) == expected @pytest.mark.parametrize("arg, expected", [ - [Period((periods.DAY, Instant((1, 1, 1)), 365)), "100_365"], - [Period((periods.MONTH, Instant((1, 1, 1)), 12)), "200_12"], - [Period((periods.YEAR, Instant((1, 1, 1)), 2)), "300_2"], - [Period((periods.ETERNITY, Instant((1, 1, 1)), 1)), "400_1"], + [periods.Period((periods.DAY, periods.Instant((1, 1, 1)), 365)), "100_365"], + [periods.Period((periods.MONTH, periods.Instant((1, 1, 1)), 12)), "200_12"], + [periods.Period((periods.YEAR, periods.Instant((1, 1, 1)), 2)), "300_2"], + [periods.Period((periods.ETERNITY, periods.Instant((1, 1, 1)), 1)), "400_1"], ]) def test_key_period_size_with_a_valid_argument(arg, expected): assert periods.key_period_size(arg) == expected diff --git a/openfisca_core/periods/tests/test_instant.py b/openfisca_core/periods/tests/test_instant.py index 31db048c93..a0c098cc32 100644 --- a/openfisca_core/periods/tests/test_instant.py +++ b/openfisca_core/periods/tests/test_instant.py @@ -1,27 +1,26 @@ import pytest from openfisca_core import periods -from openfisca_core.periods import Instant @pytest.fixture def instant(): - return Instant((2020, 2, 29)) + return periods.Instant((2020, 2, 29)) @pytest.mark.parametrize("offset, unit, expected", [ - ["first-of", periods.YEAR, Instant((2020, 1, 1))], - ["first-of", periods.MONTH, Instant((2020, 2, 1))], - ["first-of", periods.DAY, Instant((2020, 2, 29))], - ["last-of", periods.YEAR, Instant((2020, 12, 31))], - ["last-of", periods.MONTH, Instant((2020, 2, 29))], - ["last-of", periods.DAY, Instant((2020, 2, 29))], - [-3, periods.YEAR, Instant((2017, 2, 28))], - [-3, periods.MONTH, Instant((2019, 11, 29))], - [-3, periods.DAY, Instant((2020, 2, 26))], - [3, periods.YEAR, Instant((2023, 2, 28))], - [3, periods.MONTH, Instant((2020, 5, 29))], - [3, periods.DAY, Instant((2020, 3, 3))], + ["first-of", periods.YEAR, periods.Instant((2020, 1, 1))], + ["first-of", periods.MONTH, periods.Instant((2020, 2, 1))], + ["first-of", periods.DAY, periods.Instant((2020, 2, 29))], + ["last-of", periods.YEAR, periods.Instant((2020, 12, 31))], + ["last-of", periods.MONTH, periods.Instant((2020, 2, 29))], + ["last-of", periods.DAY, periods.Instant((2020, 2, 29))], + [-3, periods.YEAR, periods.Instant((2017, 2, 28))], + [-3, periods.MONTH, periods.Instant((2019, 11, 29))], + [-3, periods.DAY, periods.Instant((2020, 2, 26))], + [3, periods.YEAR, periods.Instant((2023, 2, 28))], + [3, periods.MONTH, periods.Instant((2020, 5, 29))], + [3, periods.DAY, periods.Instant((2020, 3, 3))], ]) def test_offset(instant, offset, unit, expected): assert instant.offset(offset, unit) == expected diff --git a/openfisca_core/periods/tests/test_period.py b/openfisca_core/periods/tests/test_period.py index 16d20733b2..723c806ad3 100644 --- a/openfisca_core/periods/tests/test_period.py +++ b/openfisca_core/periods/tests/test_period.py @@ -1,113 +1,112 @@ import pytest from openfisca_core import periods -from openfisca_core.periods import Instant, Period @pytest.fixture def instant(): - return Instant((2022, 12, 31)) + return periods.Instant((2022, 12, 31)) @pytest.mark.parametrize("date_unit, instant, size, expected", [ - [periods.YEAR, Instant((2022, 1, 1)), 1, "2022"], - [periods.MONTH, Instant((2022, 1, 1)), 12, "2022"], - [periods.YEAR, Instant((2022, 3, 1)), 1, "year:2022-03"], - [periods.MONTH, Instant((2022, 3, 1)), 12, "year:2022-03"], - [periods.YEAR, Instant((2022, 1, 1)), 3, "year:2022:3"], - [periods.YEAR, Instant((2022, 1, 3)), 3, "year:2022:3"], + [periods.YEAR, periods.Instant((2022, 1, 1)), 1, "2022"], + [periods.MONTH, periods.Instant((2022, 1, 1)), 12, "2022"], + [periods.YEAR, periods.Instant((2022, 3, 1)), 1, "year:2022-03"], + [periods.MONTH, periods.Instant((2022, 3, 1)), 12, "year:2022-03"], + [periods.YEAR, periods.Instant((2022, 1, 1)), 3, "year:2022:3"], + [periods.YEAR, periods.Instant((2022, 1, 3)), 3, "year:2022:3"], ]) def test_str_with_years(date_unit, instant, size, expected): - assert str(Period((date_unit, instant, size))) == expected + assert str(periods.Period((date_unit, instant, size))) == expected @pytest.mark.parametrize("date_unit, instant, size, expected", [ - [periods.MONTH, Instant((2022, 1, 1)), 1, "2022-01"], - [periods.MONTH, Instant((2022, 1, 1)), 3, "month:2022-01:3"], - [periods.MONTH, Instant((2022, 3, 1)), 3, "month:2022-03:3"], + [periods.MONTH, periods.Instant((2022, 1, 1)), 1, "2022-01"], + [periods.MONTH, periods.Instant((2022, 1, 1)), 3, "month:2022-01:3"], + [periods.MONTH, periods.Instant((2022, 3, 1)), 3, "month:2022-03:3"], ]) def test_str_with_months(date_unit, instant, size, expected): - assert str(Period((date_unit, instant, size))) == expected + assert str(periods.Period((date_unit, instant, size))) == expected @pytest.mark.parametrize("date_unit, instant, size, expected", [ - [periods.DAY, Instant((2022, 1, 1)), 1, "2022-01-01"], - [periods.DAY, Instant((2022, 1, 1)), 3, "day:2022-01-01:3"], - [periods.DAY, Instant((2022, 3, 1)), 3, "day:2022-03-01:3"], + [periods.DAY, periods.Instant((2022, 1, 1)), 1, "2022-01-01"], + [periods.DAY, periods.Instant((2022, 1, 1)), 3, "day:2022-01-01:3"], + [periods.DAY, periods.Instant((2022, 3, 1)), 3, "day:2022-03-01:3"], ]) def test_str_with_days(date_unit, instant, size, expected): - assert str(Period((date_unit, instant, size))) == expected + assert str(periods.Period((date_unit, instant, size))) == expected @pytest.mark.parametrize("period_unit, unit, start, cease, count", [ - [periods.YEAR, periods.YEAR, Instant((2022, 1, 1)), Instant((2024, 1, 1)), 3], - [periods.YEAR, periods.MONTH, Instant((2022, 12, 1)), Instant((2025, 11, 1)), 36], - [periods.YEAR, periods.DAY, Instant((2022, 12, 31)), Instant((2025, 12, 30)), 1096], - [periods.MONTH, periods.MONTH, Instant((2022, 12, 1)), Instant((2023, 2, 1)), 3], - [periods.MONTH, periods.DAY, Instant((2022, 12, 31)), Instant((2023, 3, 30)), 90], - [periods.DAY, periods.DAY, Instant((2022, 12, 31)), Instant((2023, 1, 2)), 3], + [periods.YEAR, periods.YEAR, periods.Instant((2022, 1, 1)), periods.Instant((2024, 1, 1)), 3], + [periods.YEAR, periods.MONTH, periods.Instant((2022, 12, 1)), periods.Instant((2025, 11, 1)), 36], + [periods.YEAR, periods.DAY, periods.Instant((2022, 12, 31)), periods.Instant((2025, 12, 30)), 1096], + [periods.MONTH, periods.MONTH, periods.Instant((2022, 12, 1)), periods.Instant((2023, 2, 1)), 3], + [periods.MONTH, periods.DAY, periods.Instant((2022, 12, 31)), periods.Instant((2023, 3, 30)), 90], + [periods.DAY, periods.DAY, periods.Instant((2022, 12, 31)), periods.Instant((2023, 1, 2)), 3], ]) def test_subperiods(instant, period_unit, unit, start, cease, count): - period = Period((period_unit, instant, 3)) + period = periods.Period((period_unit, instant, 3)) subperiods = period.get_subperiods(unit) assert len(subperiods) == count - assert subperiods[0] == Period((unit, start, 1)) - assert subperiods[-1] == Period((unit, cease, 1)) + assert subperiods[0] == periods.Period((unit, start, 1)) + assert subperiods[-1] == periods.Period((unit, cease, 1)) @pytest.mark.parametrize("period_unit, offset, unit, expected", [ - [periods.YEAR, "first-of", periods.YEAR, Period(('year', Instant((2022, 1, 1)), 3))], - [periods.YEAR, "first-of", periods.MONTH, Period(('year', Instant((2022, 12, 1)), 3))], - [periods.YEAR, "last-of", periods.YEAR, Period(('year', Instant((2022, 12, 31)), 3))], - [periods.YEAR, "last-of", periods.MONTH, Period(('year', Instant((2022, 12, 31)), 3))], - [periods.YEAR, -3, periods.YEAR, Period(('year', Instant((2019, 12, 31)), 3))], - [periods.YEAR, 1, periods.MONTH, Period(('year', Instant((2023, 1, 31)), 3))], - [periods.YEAR, 3, periods.DAY, Period(('year', Instant((2023, 1, 3)), 3))], - [periods.MONTH, "first-of", periods.YEAR, Period(('month', Instant((2022, 1, 1)), 3))], - [periods.MONTH, "first-of", periods.MONTH, Period(('month', Instant((2022, 12, 1)), 3))], - [periods.MONTH, "last-of", periods.YEAR, Period(('month', Instant((2022, 12, 31)), 3))], - [periods.MONTH, "last-of", periods.MONTH, Period(('month', Instant((2022, 12, 31)), 3))], - [periods.MONTH, -3, periods.YEAR, Period(('month', Instant((2019, 12, 31)), 3))], - [periods.MONTH, 1, periods.MONTH, Period(('month', Instant((2023, 1, 31)), 3))], - [periods.MONTH, 3, periods.DAY, Period(('month', Instant((2023, 1, 3)), 3))], - [periods.DAY, "first-of", periods.YEAR, Period(('day', Instant((2022, 1, 1)), 3))], - [periods.DAY, "first-of", periods.MONTH, Period(('day', Instant((2022, 12, 1)), 3))], - [periods.DAY, "last-of", periods.YEAR, Period(('day', Instant((2022, 12, 31)), 3))], - [periods.DAY, "last-of", periods.MONTH, Period(('day', Instant((2022, 12, 31)), 3))], - [periods.DAY, -3, periods.YEAR, Period(('day', Instant((2019, 12, 31)), 3))], - [periods.DAY, 1, periods.MONTH, Period(('day', Instant((2023, 1, 31)), 3))], - [periods.DAY, 3, periods.DAY, Period(('day', Instant((2023, 1, 3)), 3))], + [periods.YEAR, "first-of", periods.YEAR, periods.Period(('year', periods.Instant((2022, 1, 1)), 3))], + [periods.YEAR, "first-of", periods.MONTH, periods.Period(('year', periods.Instant((2022, 12, 1)), 3))], + [periods.YEAR, "last-of", periods.YEAR, periods.Period(('year', periods.Instant((2022, 12, 31)), 3))], + [periods.YEAR, "last-of", periods.MONTH, periods.Period(('year', periods.Instant((2022, 12, 31)), 3))], + [periods.YEAR, -3, periods.YEAR, periods.Period(('year', periods.Instant((2019, 12, 31)), 3))], + [periods.YEAR, 1, periods.MONTH, periods.Period(('year', periods.Instant((2023, 1, 31)), 3))], + [periods.YEAR, 3, periods.DAY, periods.Period(('year', periods.Instant((2023, 1, 3)), 3))], + [periods.MONTH, "first-of", periods.YEAR, periods.Period(('month', periods.Instant((2022, 1, 1)), 3))], + [periods.MONTH, "first-of", periods.MONTH, periods.Period(('month', periods.Instant((2022, 12, 1)), 3))], + [periods.MONTH, "last-of", periods.YEAR, periods.Period(('month', periods.Instant((2022, 12, 31)), 3))], + [periods.MONTH, "last-of", periods.MONTH, periods.Period(('month', periods.Instant((2022, 12, 31)), 3))], + [periods.MONTH, -3, periods.YEAR, periods.Period(('month', periods.Instant((2019, 12, 31)), 3))], + [periods.MONTH, 1, periods.MONTH, periods.Period(('month', periods.Instant((2023, 1, 31)), 3))], + [periods.MONTH, 3, periods.DAY, periods.Period(('month', periods.Instant((2023, 1, 3)), 3))], + [periods.DAY, "first-of", periods.YEAR, periods.Period(('day', periods.Instant((2022, 1, 1)), 3))], + [periods.DAY, "first-of", periods.MONTH, periods.Period(('day', periods.Instant((2022, 12, 1)), 3))], + [periods.DAY, "last-of", periods.YEAR, periods.Period(('day', periods.Instant((2022, 12, 31)), 3))], + [periods.DAY, "last-of", periods.MONTH, periods.Period(('day', periods.Instant((2022, 12, 31)), 3))], + [periods.DAY, -3, periods.YEAR, periods.Period(('day', periods.Instant((2019, 12, 31)), 3))], + [periods.DAY, 1, periods.MONTH, periods.Period(('day', periods.Instant((2023, 1, 31)), 3))], + [periods.DAY, 3, periods.DAY, periods.Period(('day', periods.Instant((2023, 1, 3)), 3))], ]) def test_offset(instant, period_unit, offset, unit, expected): - period = Period((period_unit, instant, 3)) + period = periods.Period((period_unit, instant, 3)) assert period.offset(offset, unit) == expected @pytest.mark.parametrize("date_unit, instant, size, expected", [ - [periods.MONTH, Instant((2022, 12, 1)), 1, 1], - [periods.MONTH, Instant((2012, 2, 3)), 1, 1], - [periods.MONTH, Instant((2022, 1, 3)), 3, 3], - [periods.MONTH, Instant((2012, 1, 3)), 3, 3], - [periods.YEAR, Instant((2022, 12, 1)), 1, 12], - [periods.YEAR, Instant((2012, 1, 1)), 1, 12], - [periods.YEAR, Instant((2022, 1, 1)), 2, 24], + [periods.MONTH, periods.Instant((2022, 12, 1)), 1, 1], + [periods.MONTH, periods.Instant((2012, 2, 3)), 1, 1], + [periods.MONTH, periods.Instant((2022, 1, 3)), 3, 3], + [periods.MONTH, periods.Instant((2012, 1, 3)), 3, 3], + [periods.YEAR, periods.Instant((2022, 12, 1)), 1, 12], + [periods.YEAR, periods.Instant((2012, 1, 1)), 1, 12], + [periods.YEAR, periods.Instant((2022, 1, 1)), 2, 24], ]) def test_day_size_in_months(date_unit, instant, size, expected): - period = Period((date_unit, instant, size)) + period = periods.Period((date_unit, instant, size)) assert period.size_in_months == expected @pytest.mark.parametrize("date_unit, instant, size, expected", [ - [periods.DAY, Instant((2022, 12, 31)), 1, 1], - [periods.DAY, Instant((2022, 12, 31)), 3, 3], - [periods.MONTH, Instant((2022, 12, 1)), 1, 31], - [periods.MONTH, Instant((2012, 2, 3)), 1, 29], - [periods.MONTH, Instant((2022, 1, 3)), 3, 31 + 28 + 31], - [periods.MONTH, Instant((2012, 1, 3)), 3, 31 + 29 + 31], - [periods.YEAR, Instant((2022, 12, 1)), 1, 365], - [periods.YEAR, Instant((2012, 1, 1)), 1, 366], - [periods.YEAR, Instant((2022, 1, 1)), 2, 730], + [periods.DAY, periods.Instant((2022, 12, 31)), 1, 1], + [periods.DAY, periods.Instant((2022, 12, 31)), 3, 3], + [periods.MONTH, periods.Instant((2022, 12, 1)), 1, 31], + [periods.MONTH, periods.Instant((2012, 2, 3)), 1, 29], + [periods.MONTH, periods.Instant((2022, 1, 3)), 3, 31 + 28 + 31], + [periods.MONTH, periods.Instant((2012, 1, 3)), 3, 31 + 29 + 31], + [periods.YEAR, periods.Instant((2022, 12, 1)), 1, 365], + [periods.YEAR, periods.Instant((2012, 1, 1)), 1, 366], + [periods.YEAR, periods.Instant((2022, 1, 1)), 2, 730], ]) def test_day_size_in_days(date_unit, instant, size, expected): - period = Period((date_unit, instant, size)) + period = periods.Period((date_unit, instant, size)) assert period.size_in_days == expected From f2e80858b48b7a905f525a7a29b0b81f385cdf24 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Wed, 14 Dec 2022 01:31:10 +0100 Subject: [PATCH 49/70] Fix style --- openfisca_core/periods/_funcs.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openfisca_core/periods/_funcs.py b/openfisca_core/periods/_funcs.py index 5299829bd3..fbc4f881bf 100644 --- a/openfisca_core/periods/_funcs.py +++ b/openfisca_core/periods/_funcs.py @@ -188,6 +188,7 @@ def parse_simple_period(value: str) -> Optional[types.Period]: else: return Period((_config.YEAR, Instant((date.year, date.month, 1)), 1)) + def period(value: Any) -> types.Period: """Build a new period, aka a triple (unit, start_instant, size). From 35b03dbab9949c4ae92eac91115a9d9213e8fcfb Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Wed, 14 Dec 2022 01:36:06 +0100 Subject: [PATCH 50/70] Rename zinstant` to `build_instant` --- CHANGELOG.md | 1 + openfisca_core/parameters/at_instant_like.py | 2 +- openfisca_core/periods/__init__.py | 2 +- openfisca_core/periods/_funcs.py | 16 ++++++++-------- .../taxbenefitsystems/tax_benefit_system.py | 2 +- 5 files changed, 12 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bfde1fd9da..4513130103 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ #### Breaking changes - Deprecate `periods.intersect`. +- Rename `instant` to `build_instant` #### Technical changes diff --git a/openfisca_core/parameters/at_instant_like.py b/openfisca_core/parameters/at_instant_like.py index 1a1db34beb..1b799b24ed 100644 --- a/openfisca_core/parameters/at_instant_like.py +++ b/openfisca_core/parameters/at_instant_like.py @@ -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 diff --git a/openfisca_core/periods/__init__.py b/openfisca_core/periods/__init__.py index 7c229e4e79..6c83ac6492 100644 --- a/openfisca_core/periods/__init__.py +++ b/openfisca_core/periods/__init__.py @@ -39,7 +39,7 @@ ) from ._funcs import ( # noqa: F401 - instant, + build_instant, instant_date, key_period_size, parse_simple_period, diff --git a/openfisca_core/periods/_funcs.py b/openfisca_core/periods/_funcs.py index fbc4f881bf..d300d24bb1 100644 --- a/openfisca_core/periods/_funcs.py +++ b/openfisca_core/periods/_funcs.py @@ -12,7 +12,7 @@ from .period_ import Period -def instant(value: Any) -> Optional[types.Instant]: +def build_instant(value: Any) -> Optional[types.Instant]: """Build a new instant, aka a triple of integers (year, month, day). Args: @@ -26,22 +26,22 @@ def instant(value: Any) -> Optional[types.Instant]: ValueError: When the arguments were invalid, like "2021-32-13". Examples: - >>> instant(datetime.date(2021, 9, 16)) + >>> build_instant(datetime.date(2021, 9, 16)) Instant((2021, 9, 16)) - >>> instant(Instant((2021, 9, 16))) + >>> build_instant(Instant((2021, 9, 16))) Instant((2021, 9, 16)) - >>> instant(Period(("year", Instant((2021, 9, 16)), 1))) + >>> build_instant(Period(("year", Instant((2021, 9, 16)), 1))) Instant((2021, 9, 16)) - >>> instant("2021") + >>> build_instant("2021") Instant((2021, 1, 1)) - >>> instant(2021) + >>> build_instant(2021) Instant((2021, 1, 1)) - >>> instant((2021, 9)) + >>> build_instant((2021, 9)) Instant((2021, 9, 1)) """ @@ -241,7 +241,7 @@ def period(value: Any) -> types.Period: return Period((_config.DAY, value, 1)) if value == "ETERNITY" or value == _config.ETERNITY: - return Period(("eternity", instant(datetime.date.min), float("inf"))) + return Period(("eternity", build_instant(datetime.date.min), float("inf"))) if isinstance(value, int): return Period((_config.YEAR, Instant((value, 1, 1)), 1)) diff --git a/openfisca_core/taxbenefitsystems/tax_benefit_system.py b/openfisca_core/taxbenefitsystems/tax_benefit_system.py index 9a8831269d..4af239ca52 100644 --- a/openfisca_core/taxbenefitsystems/tax_benefit_system.py +++ b/openfisca_core/taxbenefitsystems/tax_benefit_system.py @@ -407,7 +407,7 @@ def get_parameters_at_instant( key = instant.start elif isinstance(instant, (str, int)): - key = periods.instant(instant) + key = periods.build_instant(instant) else: msg = f"Expected an Instant (e.g. Instant((2017, 1, 1)) ). Got: {key}." From 746d29ce9ca43b60330cf187f3b1246ba69f16af Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Wed, 14 Dec 2022 01:40:44 +0100 Subject: [PATCH 51/70] Rename to --- CHANGELOG.md | 1 + .../data_storage/in_memory_storage.py | 12 +- .../data_storage/on_disk_storage.py | 14 +- openfisca_core/holders/holder.py | 2 +- openfisca_core/model_api.py | 2 +- openfisca_core/parameters/parameter.py | 2 +- openfisca_core/periods/__init__.py | 2 +- openfisca_core/periods/_funcs.py | 214 +++++++++--------- openfisca_core/periods/tests/test_helpers.py | 4 +- openfisca_core/populations/population.py | 2 +- .../scripts/measure_performances.py | 2 +- openfisca_core/simulations/simulation.py | 10 +- .../simulations/simulation_builder.py | 8 +- openfisca_core/variables/variable.py | 2 +- tests/core/test_countries.py | 2 +- tests/core/test_cycles.py | 2 +- tests/core/test_holders.py | 30 +-- tests/core/test_opt_out_cache.py | 2 +- tests/core/test_reforms.py | 34 +-- tests/core/variables/test_annualize.py | 8 +- 20 files changed, 178 insertions(+), 177 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4513130103..29a615634c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Deprecate `periods.intersect`. - Rename `instant` to `build_instant` +- Rename `period` to `build_period` #### Technical changes diff --git a/openfisca_core/data_storage/in_memory_storage.py b/openfisca_core/data_storage/in_memory_storage.py index bd40460a56..765a6f836c 100644 --- a/openfisca_core/data_storage/in_memory_storage.py +++ b/openfisca_core/data_storage/in_memory_storage.py @@ -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: @@ -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 @@ -35,8 +35,8 @@ 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 diff --git a/openfisca_core/data_storage/on_disk_storage.py b/openfisca_core/data_storage/on_disk_storage.py index 10d4696b58..402b576e6a 100644 --- a/openfisca_core/data_storage/on_disk_storage.py +++ b/openfisca_core/data_storage/on_disk_storage.py @@ -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: @@ -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' @@ -55,8 +55,8 @@ 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 = { @@ -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): diff --git a/openfisca_core/holders/holder.py b/openfisca_core/holders/holder.py index ae7e3fbcec..18784b0b91 100644 --- a/openfisca_core/holders/holder.py +++ b/openfisca_core/holders/holder.py @@ -210,7 +210,7 @@ def set_input( """ - period = periods.period(period) + period = periods.build_period(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.', diff --git a/openfisca_core/model_api.py b/openfisca_core/model_api.py index 8ccf5c2763..8e306026d3 100644 --- a/openfisca_core/model_api.py +++ b/openfisca_core/model_api.py @@ -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 diff --git a/openfisca_core/parameters/parameter.py b/openfisca_core/parameters/parameter.py index ed2c9482a4..63090bb3a6 100644 --- a/openfisca_core/parameters/parameter.py +++ b/openfisca_core/parameters/parameter.py @@ -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: diff --git a/openfisca_core/periods/__init__.py b/openfisca_core/periods/__init__.py index 6c83ac6492..956daf0a8d 100644 --- a/openfisca_core/periods/__init__.py +++ b/openfisca_core/periods/__init__.py @@ -40,10 +40,10 @@ from ._funcs import ( # noqa: F401 build_instant, + build_period, instant_date, key_period_size, parse_simple_period, - period, unit_weight, unit_weights, ) diff --git a/openfisca_core/periods/_funcs.py b/openfisca_core/periods/_funcs.py index d300d24bb1..de00fa687a 100644 --- a/openfisca_core/periods/_funcs.py +++ b/openfisca_core/periods/_funcs.py @@ -91,6 +91,113 @@ def build_instant(value: Any) -> Optional[types.Instant]: return Instant(instant) +def build_period(value: Any) -> types.Period: + """Build a new period, aka a triple (unit, start_instant, size). + + Args: + value: A ``period-like`` object. + + Returns: + :obj:`.Period`: A period. + + Raises: + :exc:`ValueError`: When the arguments were invalid, like "2021-32-13". + + Examples: + >>> build_period(Period(("year", Instant((2021, 1, 1)), 1))) + Period(('year', Instant((2021, 1, 1)), 1)) + + >>> build_period(Instant((2021, 1, 1))) + Period(('day', Instant((2021, 1, 1)), 1)) + + >>> build_period("eternity") + Period(('eternity', Instant((1, 1, 1)), inf)) + + >>> build_period(2021) + Period(('year', Instant((2021, 1, 1)), 1)) + + >>> build_period("2014") + Period(('year', Instant((2014, 1, 1)), 1)) + + >>> build_period("year:2014") + Period(('year', Instant((2014, 1, 1)), 1)) + + >>> build_period("month:2014-2") + Period(('month', Instant((2014, 2, 1)), 1)) + + >>> build_period("year:2014-2") + Period(('year', Instant((2014, 2, 1)), 1)) + + >>> build_period("day:2014-2-2") + Period(('day', Instant((2014, 2, 2)), 1)) + + >>> build_period("day:2014-2-2:3") + Period(('day', Instant((2014, 2, 2)), 3)) + + """ + + if isinstance(value, Period): + return value + + if isinstance(value, Instant): + return Period((_config.DAY, value, 1)) + + if value == "ETERNITY" or value == _config.ETERNITY: + return Period(("eternity", build_instant(datetime.date.min), float("inf"))) + + if isinstance(value, int): + return Period((_config.YEAR, Instant((value, 1, 1)), 1)) + + if not isinstance(value, str): + _raise_error(value) + + # Try to parse as a simple period + period = parse_simple_period(value) + + if period is not None: + return period + + # Complex periods must have a ':' in their strings + if ":" not in value: + _raise_error(value) + + components = value.split(":") + + # Left-most component must be a valid unit + unit = components[0] + + if unit not in (_config.DAY, _config.MONTH, _config.YEAR): + _raise_error(value) + + # Middle component must be a valid iso period + base_period = parse_simple_period(components[1]) + + if not base_period: + _raise_error(value) + + # Periods like year:2015-03 have a size of 1 + if len(components) == 2: + size = 1 + + # If provided, make sure the size is an integer + elif len(components) == 3: + try: + size = int(components[2]) + + except ValueError: + _raise_error(value) + + # If there are more than 2 ":" in the string, the period is invalid + else: + _raise_error(value) + + # Reject ambiguous periods such as month:2014 + if unit_weight(base_period.unit) > unit_weight(unit): + _raise_error(value) + + return Period((unit, base_period.start, size)) + + def instant_date(instant: Optional[types.Instant]) -> Optional[datetime.date]: """Returns the date representation of an ``Instant``. @@ -189,113 +296,6 @@ def parse_simple_period(value: str) -> Optional[types.Period]: return Period((_config.YEAR, Instant((date.year, date.month, 1)), 1)) -def period(value: Any) -> types.Period: - """Build a new period, aka a triple (unit, start_instant, size). - - Args: - value: A ``period-like`` object. - - Returns: - :obj:`.Period`: A period. - - Raises: - :exc:`ValueError`: When the arguments were invalid, like "2021-32-13". - - Examples: - >>> period(Period(("year", Instant((2021, 1, 1)), 1))) - Period(('year', Instant((2021, 1, 1)), 1)) - - >>> period(Instant((2021, 1, 1))) - Period(('day', Instant((2021, 1, 1)), 1)) - - >>> period("eternity") - Period(('eternity', Instant((1, 1, 1)), inf)) - - >>> period(2021) - Period(('year', Instant((2021, 1, 1)), 1)) - - >>> period("2014") - Period(('year', Instant((2014, 1, 1)), 1)) - - >>> period("year:2014") - Period(('year', Instant((2014, 1, 1)), 1)) - - >>> period("month:2014-2") - Period(('month', Instant((2014, 2, 1)), 1)) - - >>> period("year:2014-2") - Period(('year', Instant((2014, 2, 1)), 1)) - - >>> period("day:2014-2-2") - Period(('day', Instant((2014, 2, 2)), 1)) - - >>> period("day:2014-2-2:3") - Period(('day', Instant((2014, 2, 2)), 3)) - - """ - - if isinstance(value, Period): - return value - - if isinstance(value, Instant): - return Period((_config.DAY, value, 1)) - - if value == "ETERNITY" or value == _config.ETERNITY: - return Period(("eternity", build_instant(datetime.date.min), float("inf"))) - - if isinstance(value, int): - return Period((_config.YEAR, Instant((value, 1, 1)), 1)) - - if not isinstance(value, str): - _raise_error(value) - - # Try to parse as a simple period - period = parse_simple_period(value) - - if period is not None: - return period - - # Complex periods must have a ':' in their strings - if ":" not in value: - _raise_error(value) - - components = value.split(":") - - # Left-most component must be a valid unit - unit = components[0] - - if unit not in (_config.DAY, _config.MONTH, _config.YEAR): - _raise_error(value) - - # Middle component must be a valid iso period - base_period = parse_simple_period(components[1]) - - if not base_period: - _raise_error(value) - - # Periods like year:2015-03 have a size of 1 - if len(components) == 2: - size = 1 - - # If provided, make sure the size is an integer - elif len(components) == 3: - try: - size = int(components[2]) - - except ValueError: - _raise_error(value) - - # If there are more than 2 ":" in the string, the period is invalid - else: - _raise_error(value) - - # Reject ambiguous periods such as month:2014 - if unit_weight(base_period.unit) > unit_weight(unit): - _raise_error(value) - - return Period((unit, base_period.start, size)) - - def unit_weights() -> Dict[str, int]: """Assign weights to date units. diff --git a/openfisca_core/periods/tests/test_helpers.py b/openfisca_core/periods/tests/test_helpers.py index 149b0a82fa..333186f3e8 100644 --- a/openfisca_core/periods/tests/test_helpers.py +++ b/openfisca_core/periods/tests/test_helpers.py @@ -91,7 +91,7 @@ def test_instant_date_with_an_invalid_argument(arg, error): ["day:1000-01-01:3", periods.Period((periods.DAY, periods.Instant((1000, 1, 1)), 3))], ]) def test_period(arg, expected): - assert periods.period(arg) == expected + assert periods.build_period(arg) == expected @pytest.mark.parametrize("arg, error", [ @@ -114,7 +114,7 @@ def test_period(arg, expected): ]) def test_period_with_an_invalid_argument(arg, error): with pytest.raises(error): - periods.period(arg) + periods.build_period(arg) @pytest.mark.parametrize("arg, expected", [ diff --git a/openfisca_core/populations/population.py b/openfisca_core/populations/population.py index cb243aff70..edf08044f0 100644 --- a/openfisca_core/populations/population.py +++ b/openfisca_core/populations/population.py @@ -111,7 +111,7 @@ def __call__( calculate: Calculate = Calculate( variable = variable_name, - period = periods.period(period), + period = periods.build_period(period), option = options, ) diff --git a/openfisca_core/scripts/measure_performances.py b/openfisca_core/scripts/measure_performances.py index 5c4fced850..5e0e01a218 100644 --- a/openfisca_core/scripts/measure_performances.py +++ b/openfisca_core/scripts/measure_performances.py @@ -186,7 +186,7 @@ def formula(self, simulation, period): @timeit def check_revenu_disponible(year, city_code, expected_revenu_disponible): - simulation = simulations.Simulation(period = periods.period(year), tax_benefit_system = tax_benefit_system) + simulation = simulations.Simulation(period = periods.build_period(year), tax_benefit_system = tax_benefit_system) famille = simulation.populations["famille"] famille.count = 3 famille.roles_count = 2 diff --git a/openfisca_core/simulations/simulation.py b/openfisca_core/simulations/simulation.py index ef1cbbd869..17cb73ebf7 100644 --- a/openfisca_core/simulations/simulation.py +++ b/openfisca_core/simulations/simulation.py @@ -96,7 +96,7 @@ def calculate(self, variable_name: str, period): """Calculate ``variable_name`` for ``period``.""" if period is not None and not isinstance(period, Period): - period = periods.period(period) + period = periods.build_period(period) self.tracer.record_calculation_start(variable_name, period) @@ -168,7 +168,7 @@ def calculate_add(self, variable_name: str, period): raise VariableNotFoundError(variable_name, self.tax_benefit_system) if period is not None and not isinstance(period, Period): - period = periods.period(period) + period = periods.build_period(period) # Check that the requested period matches definition_period if periods.unit_weight(variable.definition_period) > periods.unit_weight(period.unit): @@ -197,7 +197,7 @@ def calculate_divide(self, variable_name: str, period): raise VariableNotFoundError(variable_name, self.tax_benefit_system) if period is not None and not isinstance(period, Period): - period = periods.period(period) + period = periods.build_period(period) # Check that the requested period matches definition_period if variable.definition_period != periods.YEAR: @@ -345,7 +345,7 @@ def get_array(self, variable_name: str, period): Unlike :meth:`.calculate`, this method *does not* trigger calculations and *does not* use any formula. """ if period is not None and not isinstance(period, Period): - period = periods.period(period) + period = periods.build_period(period) return self.get_holder(variable_name).get_array(period) def get_holder(self, variable_name: str): @@ -438,7 +438,7 @@ def set_input(self, variable_name: str, period, value): if variable is None: raise VariableNotFoundError(variable_name, self.tax_benefit_system) - period = periods.period(period) + period = periods.build_period(period) if ((variable.end is not None) and (period.start.date > variable.end)): return self.get_holder(variable_name).set_input(period, value) diff --git a/openfisca_core/simulations/simulation_builder.py b/openfisca_core/simulations/simulation_builder.py index 0092ba8371..71193296b3 100644 --- a/openfisca_core/simulations/simulation_builder.py +++ b/openfisca_core/simulations/simulation_builder.py @@ -325,7 +325,7 @@ def add_group_entity(self, persons_plural, persons_ids, entity, instances_json): def set_default_period(self, period_str): if period_str: - self.default_period = str(periods.period(period_str)) + self.default_period = str(periods.build_period(period_str)) def get_input(self, variable, period_str): if variable not in self.input_buffer: @@ -368,7 +368,7 @@ def init_variable_values(self, entity, instance_object, instance_id): for period_str, value in variable_values.items(): try: - periods.period(period_str) + periods.build_period(period_str) except ValueError as e: raise SituationParsingError(path_in_json, e.args[0]) variable = entity.get_variable(variable_name) @@ -393,7 +393,7 @@ def add_variable_value(self, entity, variable, instance_index, instance_id, peri array[instance_index] = value - self.input_buffer[variable.name][str(periods.period(period_str))] = array + self.input_buffer[variable.name][str(periods.build_period(period_str))] = array def finalize_variables_init(self, population): # Due to set_input mechanism, we must bufferize all inputs, then actually set them, @@ -411,7 +411,7 @@ def finalize_variables_init(self, population): except ValueError: # Wrong entity, we can just ignore that continue buffer = self.input_buffer[variable_name] - unsorted_periods = [periods.period(period_str) for period_str in self.input_buffer[variable_name].keys()] + unsorted_periods = [periods.build_period(period_str) for period_str in self.input_buffer[variable_name].keys()] # We need to handle small periods first for set_input to work sorted_periods = sorted(unsorted_periods, key = periods.key_period_size) for period_value in sorted_periods: diff --git a/openfisca_core/variables/variable.py b/openfisca_core/variables/variable.py index 1e9fce3083..eeb9c33169 100644 --- a/openfisca_core/variables/variable.py +++ b/openfisca_core/variables/variable.py @@ -336,7 +336,7 @@ def get_formula( instant = period.start else: try: - instant = periods.period(period).start + instant = periods.build_period(period).start except ValueError: instant = periods.instant(period) diff --git a/tests/core/test_countries.py b/tests/core/test_countries.py index aeb4d762c7..70390e783c 100644 --- a/tests/core/test_countries.py +++ b/tests/core/test_countries.py @@ -5,7 +5,7 @@ from openfisca_core.simulations import SimulationBuilder from openfisca_core.variables import Variable -PERIOD = periods.period("2016-01") +PERIOD = periods.build_period("2016-01") @pytest.mark.parametrize("simulation", [({"salary": 2000}, PERIOD)], indirect = True) diff --git a/tests/core/test_cycles.py b/tests/core/test_cycles.py index 1c4361ded2..dc17aee5de 100644 --- a/tests/core/test_cycles.py +++ b/tests/core/test_cycles.py @@ -10,7 +10,7 @@ @pytest.fixture def reference_period(): - return periods.period('2013-01') + return periods.build_period('2013-01') @pytest.fixture diff --git a/tests/core/test_holders.py b/tests/core/test_holders.py index 907aefceb5..928683adcb 100644 --- a/tests/core/test_holders.py +++ b/tests/core/test_holders.py @@ -26,7 +26,7 @@ def couple(tax_benefit_system): build_from_entities(tax_benefit_system, situation_examples.couple) -period = periods.period('2017-12') +period = periods.build_period('2017-12') def test_set_input_enum_string(couple): @@ -89,7 +89,7 @@ def test_permanent_variable_filled(single): simulation = single holder = simulation.person.get_holder('birth') value = numpy.asarray(['1980-01-01'], dtype = holder.variable.dtype) - holder.set_input(periods.period(periods.ETERNITY), value) + holder.set_input(periods.build_period(periods.ETERNITY), value) assert holder.get_array(None) == value assert holder.get_array(periods.ETERNITY) == value assert holder.get_array('2016-01') == value @@ -98,8 +98,8 @@ def test_permanent_variable_filled(single): def test_delete_arrays(single): simulation = single salary_holder = simulation.person.get_holder('salary') - salary_holder.set_input(periods.period(2017), numpy.asarray([30000])) - salary_holder.set_input(periods.period(2018), numpy.asarray([60000])) + salary_holder.set_input(periods.build_period(2017), numpy.asarray([30000])) + salary_holder.set_input(periods.build_period(2018), numpy.asarray([60000])) assert simulation.person('salary', '2017-01') == 2500 assert simulation.person('salary', '2018-01') == 5000 salary_holder.delete_arrays(period = 2018) @@ -109,7 +109,7 @@ def test_delete_arrays(single): salary_array = simulation.get_array('salary', '2018-01') assert salary_array is None - salary_holder.set_input(periods.period(2018), numpy.asarray([15000])) + salary_holder.set_input(periods.build_period(2018), numpy.asarray([15000])) assert simulation.person('salary', '2017-01') == 2500 assert simulation.person('salary', '2018-01') == 1250 @@ -119,7 +119,7 @@ def test_get_memory_usage(single): salary_holder = simulation.person.get_holder('salary') memory_usage = salary_holder.get_memory_usage() assert memory_usage['total_nb_bytes'] == 0 - salary_holder.set_input(periods.period(2017), numpy.asarray([30000])) + salary_holder.set_input(periods.build_period(2017), numpy.asarray([30000])) memory_usage = salary_holder.get_memory_usage() assert memory_usage['nb_cells_by_array'] == 1 assert memory_usage['cell_size'] == 4 # float 32 @@ -132,7 +132,7 @@ def test_get_memory_usage_with_trace(single): simulation = single simulation.trace = True salary_holder = simulation.person.get_holder('salary') - salary_holder.set_input(periods.period(2017), numpy.asarray([30000])) + salary_holder.set_input(periods.build_period(2017), numpy.asarray([30000])) simulation.calculate('salary', '2017-01') simulation.calculate('salary', '2017-01') simulation.calculate('salary', '2017-02') @@ -147,7 +147,7 @@ def test_set_input_dispatch_by_period(single): variable = simulation.tax_benefit_system.get_variable('housing_occupancy_status') entity = simulation.household holder = Holder(variable, entity) - holders.set_input_dispatch_by_period(holder, periods.period(2019), 'owner') + holders.set_input_dispatch_by_period(holder, periods.build_period(2019), 'owner') assert holder.get_array('2019-01') == holder.get_array('2019-12') # Check the feature assert holder.get_array('2019-01') is holder.get_array('2019-12') # Check that the vectors are the same in memory, to avoid duplication @@ -159,12 +159,12 @@ def test_delete_arrays_on_disk(single): simulation = single simulation.memory_config = force_storage_on_disk salary_holder = simulation.person.get_holder('salary') - salary_holder.set_input(periods.period(2017), numpy.asarray([30000])) - salary_holder.set_input(periods.period(2018), numpy.asarray([60000])) + salary_holder.set_input(periods.build_period(2017), numpy.asarray([30000])) + salary_holder.set_input(periods.build_period(2018), numpy.asarray([60000])) assert simulation.person('salary', '2017-01') == 2500 assert simulation.person('salary', '2018-01') == 5000 salary_holder.delete_arrays(period = 2018) - salary_holder.set_input(periods.period(2018), numpy.asarray([15000])) + salary_holder.set_input(periods.build_period(2018), numpy.asarray([15000])) assert simulation.person('salary', '2017-01') == 2500 assert simulation.person('salary', '2018-01') == 1250 @@ -172,7 +172,7 @@ def test_delete_arrays_on_disk(single): def test_cache_disk(couple): simulation = couple simulation.memory_config = force_storage_on_disk - month = periods.period('2017-01') + month = periods.build_period('2017-01') holder = simulation.person.get_holder('disposable_income') data = numpy.asarray([2000, 3000]) holder.put_in_cache(data, month) @@ -183,8 +183,8 @@ def test_cache_disk(couple): def test_known_periods(couple): simulation = couple simulation.memory_config = force_storage_on_disk - month = periods.period('2017-01') - month_2 = periods.period('2017-02') + month = periods.build_period('2017-01') + month_2 = periods.build_period('2017-02') holder = simulation.person.get_holder('disposable_income') data = numpy.asarray([2000, 3000]) holder.put_in_cache(data, month) @@ -196,7 +196,7 @@ def test_known_periods(couple): def test_cache_enum_on_disk(single): simulation = single simulation.memory_config = force_storage_on_disk - month = periods.period('2017-01') + month = periods.build_period('2017-01') simulation.calculate('housing_occupancy_status', month) # First calculation housing_occupancy_status = simulation.calculate('housing_occupancy_status', month) # Read from cache assert housing_occupancy_status == housing.HousingOccupancyStatus.tenant diff --git a/tests/core/test_opt_out_cache.py b/tests/core/test_opt_out_cache.py index b4eab3e5a5..a3a2cf7a31 100644 --- a/tests/core/test_opt_out_cache.py +++ b/tests/core/test_opt_out_cache.py @@ -7,7 +7,7 @@ from openfisca_core.variables import Variable -PERIOD = periods.period("2016-01") +PERIOD = periods.build_period("2016-01") class input(Variable): diff --git a/tests/core/test_reforms.py b/tests/core/test_reforms.py index 8735cee18f..85b54abadf 100644 --- a/tests/core/test_reforms.py +++ b/tests/core/test_reforms.py @@ -124,23 +124,23 @@ def check_update_items(description, value_history, start_instant, stop_instant, check_update_items( 'Replace an item by a new item', ValuesHistory('dummy_name', {"2013-01-01": {'value': 0.0}, "2014-01-01": {'value': None}}), - periods.period(2013).start, - periods.period(2013).stop, + periods.build_period(2013).start, + periods.build_period(2013).stop, 1.0, ValuesHistory('dummy_name', {"2013-01-01": {'value': 1.0}, "2014-01-01": {'value': None}}), ) check_update_items( 'Replace an item by a new item in a list of items, the last being open', ValuesHistory('dummy_name', {"2014-01-01": {'value': 9.53}, "2015-01-01": {'value': 9.61}, "2016-01-01": {'value': 9.67}}), - periods.period(2015).start, - periods.period(2015).stop, + periods.build_period(2015).start, + periods.build_period(2015).stop, 1.0, ValuesHistory('dummy_name', {"2014-01-01": {'value': 9.53}, "2015-01-01": {'value': 1.0}, "2016-01-01": {'value': 9.67}}), ) check_update_items( 'Open the stop instant to the future', ValuesHistory('dummy_name', {"2013-01-01": {'value': 0.0}, "2014-01-01": {'value': None}}), - periods.period(2013).start, + periods.build_period(2013).start, None, # stop instant 1.0, ValuesHistory('dummy_name', {"2013-01-01": {'value': 1.0}}), @@ -148,15 +148,15 @@ def check_update_items(description, value_history, start_instant, stop_instant, check_update_items( 'Insert a new item in the middle of an existing item', ValuesHistory('dummy_name', {"2010-01-01": {'value': 0.0}, "2014-01-01": {'value': None}}), - periods.period(2011).start, - periods.period(2011).stop, + periods.build_period(2011).start, + periods.build_period(2011).stop, 1.0, ValuesHistory('dummy_name', {"2010-01-01": {'value': 0.0}, "2011-01-01": {'value': 1.0}, "2012-01-01": {'value': 0.0}, "2014-01-01": {'value': None}}), ) check_update_items( 'Insert a new open item coming after the last open item', ValuesHistory('dummy_name', {"2006-01-01": {'value': 0.055}, "2014-01-01": {'value': 0.14}}), - periods.period(2015).start, + periods.build_period(2015).start, None, # stop instant 1.0, ValuesHistory('dummy_name', {"2006-01-01": {'value': 0.055}, "2014-01-01": {'value': 0.14}, "2015-01-01": {'value': 1.0}}), @@ -164,15 +164,15 @@ def check_update_items(description, value_history, start_instant, stop_instant, check_update_items( 'Insert a new item starting at the same date than the last open item', ValuesHistory('dummy_name', {"2006-01-01": {'value': 0.055}, "2014-01-01": {'value': 0.14}}), - periods.period(2014).start, - periods.period(2014).stop, + periods.build_period(2014).start, + periods.build_period(2014).stop, 1.0, ValuesHistory('dummy_name', {"2006-01-01": {'value': 0.055}, "2014-01-01": {'value': 1.0}, "2015-01-01": {'value': 0.14}}), ) check_update_items( 'Insert a new open item starting at the same date than the last open item', ValuesHistory('dummy_name', {"2006-01-01": {'value': 0.055}, "2014-01-01": {'value': 0.14}}), - periods.period(2014).start, + periods.build_period(2014).start, None, # stop instant 1.0, ValuesHistory('dummy_name', {"2006-01-01": {'value': 0.055}, "2014-01-01": {'value': 1.0}}), @@ -180,23 +180,23 @@ def check_update_items(description, value_history, start_instant, stop_instant, check_update_items( 'Insert a new item coming before the first item', ValuesHistory('dummy_name', {"2006-01-01": {'value': 0.055}, "2014-01-01": {'value': 0.14}}), - periods.period(2005).start, - periods.period(2005).stop, + periods.build_period(2005).start, + periods.build_period(2005).stop, 1.0, ValuesHistory('dummy_name', {"2005-01-01": {'value': 1.0}, "2006-01-01": {'value': 0.055}, "2014-01-01": {'value': 0.14}}), ) check_update_items( 'Insert a new item coming before the first item with a hole', ValuesHistory('dummy_name', {"2006-01-01": {'value': 0.055}, "2014-01-01": {'value': 0.14}}), - periods.period(2003).start, - periods.period(2003).stop, + periods.build_period(2003).start, + periods.build_period(2003).stop, 1.0, ValuesHistory('dummy_name', {"2003-01-01": {'value': 1.0}, "2004-01-01": {'value': None}, "2006-01-01": {'value': 0.055}, "2014-01-01": {'value': 0.14}}), ) check_update_items( 'Insert a new open item starting before the start date of the first item', ValuesHistory('dummy_name', {"2006-01-01": {'value': 0.055}, "2014-01-01": {'value': 0.14}}), - periods.period(2005).start, + periods.build_period(2005).start, None, # stop instant 1.0, ValuesHistory('dummy_name', {"2005-01-01": {'value': 1.0}}), @@ -204,7 +204,7 @@ def check_update_items(description, value_history, start_instant, stop_instant, check_update_items( 'Insert a new open item starting at the same date than the first item', ValuesHistory('dummy_name', {"2006-01-01": {'value': 0.055}, "2014-01-01": {'value': 0.14}}), - periods.period(2006).start, + periods.build_period(2006).start, None, # stop instant 1.0, ValuesHistory('dummy_name', {"2006-01-01": {'value': 1.0}}), diff --git a/tests/core/variables/test_annualize.py b/tests/core/variables/test_annualize.py index 056fcfead3..8479c09357 100644 --- a/tests/core/variables/test_annualize.py +++ b/tests/core/variables/test_annualize.py @@ -41,7 +41,7 @@ def __call__(self, variable_name: str, period): def test_without_annualize(monthly_variable): - period = periods.period(2019) + period = periods.build_period(2019) person = PopulationMock(monthly_variable) @@ -55,7 +55,7 @@ def test_without_annualize(monthly_variable): def test_with_annualize(monthly_variable): - period = periods.period(2019) + period = periods.build_period(2019) annualized_variable = get_annualized_variable(monthly_variable) person = PopulationMock(annualized_variable) @@ -70,8 +70,8 @@ def test_with_annualize(monthly_variable): def test_with_partial_annualize(monthly_variable): - period = periods.period('year:2018:2') - annualized_variable = get_annualized_variable(monthly_variable, periods.period(2018)) + period = periods.build_period('year:2018:2') + annualized_variable = get_annualized_variable(monthly_variable, periods.build_period(2018)) person = PopulationMock(annualized_variable) From 03ce49b06a1a8f9dc4c0de7f403babef0ca674b0 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Wed, 14 Dec 2022 01:56:37 +0100 Subject: [PATCH 52/70] Move to --- CHANGELOG.md | 1 + openfisca_core/periods/__init__.py | 1 - openfisca_core/periods/_funcs.py | 28 --------------- openfisca_core/periods/instant_.py | 30 +++++++++++++++- openfisca_core/periods/tests/test_helpers.py | 37 ++++---------------- openfisca_core/periods/tests/test_instant.py | 25 +++++++++++++ setup.cfg | 2 +- 7 files changed, 63 insertions(+), 61 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29a615634c..14c094800a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - Deprecate `periods.intersect`. - Rename `instant` to `build_instant` - Rename `period` to `build_period` +- Move `instant_date` to `Instant.to_date` #### Technical changes diff --git a/openfisca_core/periods/__init__.py b/openfisca_core/periods/__init__.py index 956daf0a8d..85fd678d8c 100644 --- a/openfisca_core/periods/__init__.py +++ b/openfisca_core/periods/__init__.py @@ -41,7 +41,6 @@ from ._funcs import ( # noqa: F401 build_instant, build_period, - instant_date, key_period_size, parse_simple_period, unit_weight, diff --git a/openfisca_core/periods/_funcs.py b/openfisca_core/periods/_funcs.py index de00fa687a..655ada86ad 100644 --- a/openfisca_core/periods/_funcs.py +++ b/openfisca_core/periods/_funcs.py @@ -198,34 +198,6 @@ def build_period(value: Any) -> types.Period: return Period((unit, base_period.start, size)) -def instant_date(instant: Optional[types.Instant]) -> Optional[datetime.date]: - """Returns the date representation of an ``Instant``. - - Args: - instant (:obj:`.Instant`, optional): - An ``instant`` to get the date from. - - Returns: - None: When ``instant`` is None. - datetime.date: Otherwise. - - Examples: - >>> instant_date(Instant((2021, 1, 1))) - datetime.date(2021, 1, 1) - - """ - - if instant is None: - return None - - instant_date = _config.date_by_instant_cache.get(instant) - - if instant_date is None: - _config.date_by_instant_cache[instant] = instant_date = datetime.date(*instant) - - return instant_date - - def key_period_size(period: types.Period) -> str: """Define a key in order to sort periods by length. diff --git a/openfisca_core/periods/instant_.py b/openfisca_core/periods/instant_.py index 0a1f677006..aa8ad29d76 100644 --- a/openfisca_core/periods/instant_.py +++ b/openfisca_core/periods/instant_.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Union +from typing import Optional, Union import calendar import datetime @@ -144,6 +144,34 @@ def date(self) -> datetime.date: return instant_date + @staticmethod + def to_date(instant: Optional[types.Instant]) -> Optional[datetime.date]: + """Returns the date representation of an ``Instant``. + + Args: + instant (:obj:`.Instant`, optional): + An ``instant`` to get the date from. + + Returns: + None: When ``instant`` is None. + datetime.date: Otherwise. + + Examples: + >>> Instant.to_date(Instant((2021, 1, 1))) + datetime.date(2021, 1, 1) + + """ + + if instant is None: + return None + + date = _config.date_by_instant_cache.get(instant) + + if date is None: + _config.date_by_instant_cache[instant] = date = datetime.date(*instant) + + return date + def offset(self, offset: Union[str, int], unit: str) -> types.Instant: """Increments/decrements the given instant with offset units. diff --git a/openfisca_core/periods/tests/test_helpers.py b/openfisca_core/periods/tests/test_helpers.py index 333186f3e8..1951c7eeb6 100644 --- a/openfisca_core/periods/tests/test_helpers.py +++ b/openfisca_core/periods/tests/test_helpers.py @@ -15,8 +15,8 @@ ["1000-01", periods.Instant((1000, 1, 1))], ["1000-01-01", periods.Instant((1000, 1, 1))], ]) -def test_instant(arg, expected): - assert periods.instant(arg) == expected +def test_build_instant(arg, expected): + assert periods.build_instant(arg) == expected @pytest.mark.parametrize("arg, error", [ @@ -36,32 +36,9 @@ def test_instant(arg, expected): ["year:1000-01-01:1", ValueError], ["year:1000-01-01:3", ValueError], ]) -def test_instant_with_an_invalid_argument(arg, error): +def test_build_instant_with_an_invalid_argument(arg, error): with pytest.raises(error): - periods.instant(arg) - - -@pytest.mark.parametrize("arg, expected", [ - [None, None], - [periods.Instant((1, 1, 1)), datetime.date(1, 1, 1)], - [periods.Instant((4, 2, 29)), datetime.date(4, 2, 29)], - [(1, 1, 1), datetime.date(1, 1, 1)], - ]) -def test_instant_date(arg, expected): - assert periods.instant_date(arg) == expected - - -@pytest.mark.parametrize("arg, error", [ - [periods.Instant((-1, 1, 1)), ValueError], - [periods.Instant((1, -1, 1)), ValueError], - [periods.Instant((1, 1, -1)), ValueError], - [periods.Instant((1, 13, 1)), ValueError], - [periods.Instant((1, 1, 32)), ValueError], - [periods.Instant((1, 2, 29)), ValueError], - ]) -def test_instant_date_with_an_invalid_argument(arg, error): - with pytest.raises(error): - periods.instant_date(arg) + periods.build_instant(arg) @pytest.mark.parametrize("arg, expected", [ @@ -90,7 +67,7 @@ def test_instant_date_with_an_invalid_argument(arg, error): ["day:1000-01-01", periods.Period((periods.DAY, periods.Instant((1000, 1, 1)), 1))], ["day:1000-01-01:3", periods.Period((periods.DAY, periods.Instant((1000, 1, 1)), 3))], ]) -def test_period(arg, expected): +def test_build_period(arg, expected): assert periods.build_period(arg) == expected @@ -112,7 +89,7 @@ def test_period(arg, expected): ["day:1000-01", ValueError], ["day:1000-01:1", ValueError], ]) -def test_period_with_an_invalid_argument(arg, error): +def test_build_period_with_an_invalid_argument(arg, error): with pytest.raises(error): periods.build_period(arg) @@ -128,7 +105,7 @@ def test_period_with_an_invalid_argument(arg, error): ["1000-01-01", periods.Period((periods.DAY, periods.Instant((1000, 1, 1)), 1))], ["1000-01-99", None], ]) -def testparse_simple_period(arg, expected): +def test_parse_simple_period(arg, expected): assert periods.parse_simple_period(arg) == expected diff --git a/openfisca_core/periods/tests/test_instant.py b/openfisca_core/periods/tests/test_instant.py index a0c098cc32..cc17e5bf8b 100644 --- a/openfisca_core/periods/tests/test_instant.py +++ b/openfisca_core/periods/tests/test_instant.py @@ -1,3 +1,5 @@ +import datetime + import pytest from openfisca_core import periods @@ -8,6 +10,29 @@ def instant(): return periods.Instant((2020, 2, 29)) +@pytest.mark.parametrize("arg, expected", [ + [None, None], + [periods.Instant((1, 1, 1)), datetime.date(1, 1, 1)], + [periods.Instant((4, 2, 29)), datetime.date(4, 2, 29)], + [(1, 1, 1), datetime.date(1, 1, 1)], + ]) +def test_to_date(arg, expected): + assert periods.Instant.to_date(arg) == expected + + +@pytest.mark.parametrize("arg, error", [ + [periods.Instant((-1, 1, 1)), ValueError], + [periods.Instant((1, -1, 1)), ValueError], + [periods.Instant((1, 1, -1)), ValueError], + [periods.Instant((1, 13, 1)), ValueError], + [periods.Instant((1, 1, 32)), ValueError], + [periods.Instant((1, 2, 29)), ValueError], + ]) +def test_to_date_with_an_invalid_argument(arg, error): + with pytest.raises(error): + periods.Instant.to_date(arg) + + @pytest.mark.parametrize("offset, unit, expected", [ ["first-of", periods.YEAR, periods.Instant((2020, 1, 1))], ["first-of", periods.MONTH, periods.Instant((2020, 2, 1))], diff --git a/setup.cfg b/setup.cfg index 2144a48610..d66ba84ea3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -41,7 +41,7 @@ skip_empty = true addopts = --doctest-modules --disable-pytest-warnings --showlocals doctest_optionflags = ELLIPSIS IGNORE_EXCEPTION_DETAIL NUMBER NORMALIZE_WHITESPACE python_files = **/*.py -testpaths = tests +testpaths = openfisca_core/commons/tests openfisca_core/holders/tests openfisca_core/periods/tests tests [mypy] ignore_missing_imports = True From 4afec7a0de8eaefe77465001d3244c1c0325e41a Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Wed, 14 Dec 2022 02:24:16 +0100 Subject: [PATCH 53/70] Do not expose Instant cache --- openfisca_core/periods/__init__.py | 3 -- openfisca_core/periods/_config.py | 4 -- openfisca_core/periods/instant_.py | 81 ++++++++++++++++-------------- openfisca_core/periods/period_.py | 2 +- 4 files changed, 43 insertions(+), 47 deletions(-) diff --git a/openfisca_core/periods/__init__.py b/openfisca_core/periods/__init__.py index 85fd678d8c..a4402787dc 100644 --- a/openfisca_core/periods/__init__.py +++ b/openfisca_core/periods/__init__.py @@ -33,9 +33,6 @@ YEAR, ETERNITY, INSTANT_PATTERN, - date_by_instant_cache, - str_by_instant_cache, - year_or_month_or_day_re, ) from ._funcs import ( # noqa: F401 diff --git a/openfisca_core/periods/_config.py b/openfisca_core/periods/_config.py index 657831d527..92c8497767 100644 --- a/openfisca_core/periods/_config.py +++ b/openfisca_core/periods/_config.py @@ -12,7 +12,3 @@ # Matches "2015", "2015-01", "2015-01-01" # Does not match "2015-13", "2015-12-32" INSTANT_PATTERN: Pattern = re.compile(r"^\d{4}(-(0[1-9]|1[012]))?(-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01]))?$") - -date_by_instant_cache: Dict = {} -str_by_instant_cache: Dict = {} -year_or_month_or_day_re: Pattern = re.compile(r'(18|19|20)\d{2}(-(0?[1-9]|1[0-2])(-([0-2]?\d|3[0-1]))?)?$') diff --git a/openfisca_core/periods/instant_.py b/openfisca_core/periods/instant_.py index aa8ad29d76..be547f2c1c 100644 --- a/openfisca_core/periods/instant_.py +++ b/openfisca_core/periods/instant_.py @@ -1,13 +1,13 @@ from __future__ import annotations -from typing import Optional, Union +from typing import Any, Dict, Optional, Union import calendar import datetime from openfisca_core import types -from . import _config +from . import _config, _funcs class Instant(tuple): @@ -64,16 +64,47 @@ class Instant(tuple): """ + #: A cache with the ``datetime.date`` representation of the ``Instant``. + dates: Dict[Instant, datetime.date] = {} + + #: A cache with the ``str`` representation of the ``Instant``. + strings: Dict[Instant, str] = {} + def __repr__(self) -> str: - return f"{self.__class__.__name__}({super(Instant, self).__repr__()})" + return f"{Instant.__name__}({super(Instant, self).__repr__()})" def __str__(self) -> str: - instant_str = _config.str_by_instant_cache.get(self) + string = Instant.strings.get(self) + + if string is None: + Instant.strings[self] = self.date.isoformat() - if instant_str is None: - _config.str_by_instant_cache[self] = instant_str = self.date.isoformat() + return Instant.strings[self] + + @staticmethod + def to_date(value: Any) -> Optional[datetime.date]: + """Returns the date representation of an ``Instant``. + + Args: + value (:any:): + An ``instant-like`` object to get the date from. + + Returns: + None: When ``value`` is None. + datetime.date: Otherwise. + + Examples: + >>> Instant.to_date(Instant((2021, 1, 1))) + datetime.date(2021, 1, 1) + + """ + + instant = _funcs.build_instant(value) + + if instant is None: + return None - return instant_str + return instant.date @property def year(self) -> int: @@ -137,40 +168,12 @@ def date(self) -> datetime.date: """ - instant_date = _config.date_by_instant_cache.get(self) - - if instant_date is None: - _config.date_by_instant_cache[self] = instant_date = datetime.date(*self) - - return instant_date - - @staticmethod - def to_date(instant: Optional[types.Instant]) -> Optional[datetime.date]: - """Returns the date representation of an ``Instant``. - - Args: - instant (:obj:`.Instant`, optional): - An ``instant`` to get the date from. - - Returns: - None: When ``instant`` is None. - datetime.date: Otherwise. - - Examples: - >>> Instant.to_date(Instant((2021, 1, 1))) - datetime.date(2021, 1, 1) - - """ - - if instant is None: - return None - - date = _config.date_by_instant_cache.get(instant) + date = Instant.dates.get(self) if date is None: - _config.date_by_instant_cache[instant] = date = datetime.date(*instant) + Instant.dates[self] = datetime.date(*self) - return date + return Instant.dates[self] def offset(self, offset: Union[str, int], unit: str) -> types.Instant: """Increments/decrements the given instant with offset units. @@ -277,4 +280,4 @@ def offset(self, offset: Union[str, int], unit: str) -> types.Instant: if day > month_last_day: day = month_last_day - return self.__class__((year, month, day)) + return Instant((year, month, day)) diff --git a/openfisca_core/periods/period_.py b/openfisca_core/periods/period_.py index a858f0abe8..4f287006d1 100644 --- a/openfisca_core/periods/period_.py +++ b/openfisca_core/periods/period_.py @@ -75,7 +75,7 @@ class Period(tuple): """ def __repr__(self) -> str: - return f"{self.__class__.__name__}({super(Period, self).__repr__()})" + return f"{Period.__name__}({super(Period, self).__repr__()})" def __str__(self) -> str: """Transform period to a string. From 4a3b32d6c5b09adacced3bf45a7b860ef779d8da Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Wed, 14 Dec 2022 04:54:38 +0100 Subject: [PATCH 54/70] Fix documentation --- openfisca_core/periods/_config.py | 2 +- openfisca_core/periods/_errors.py | 0 openfisca_core/periods/_funcs.py | 64 ++++----- openfisca_core/periods/_units.py | 4 + openfisca_core/periods/instant_.py | 46 +++--- openfisca_core/periods/period_.py | 221 +++++++++++++++++++---------- openfisca_core/periods/py.typed | 0 openfisca_core/types/_domain.py | 5 + 8 files changed, 209 insertions(+), 133 deletions(-) create mode 100644 openfisca_core/periods/_errors.py create mode 100644 openfisca_core/periods/_units.py create mode 100644 openfisca_core/periods/py.typed diff --git a/openfisca_core/periods/_config.py b/openfisca_core/periods/_config.py index 92c8497767..68dc243e10 100644 --- a/openfisca_core/periods/_config.py +++ b/openfisca_core/periods/_config.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Dict, Pattern +from typing import Pattern import re diff --git a/openfisca_core/periods/_errors.py b/openfisca_core/periods/_errors.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openfisca_core/periods/_funcs.py b/openfisca_core/periods/_funcs.py index 655ada86ad..4002c1e10e 100644 --- a/openfisca_core/periods/_funcs.py +++ b/openfisca_core/periods/_funcs.py @@ -7,9 +7,7 @@ from openfisca_core import types -from . import _config -from .instant_ import Instant -from .period_ import Period +from .. import periods def build_instant(value: Any) -> Optional[types.Instant]: @@ -29,10 +27,10 @@ def build_instant(value: Any) -> Optional[types.Instant]: >>> build_instant(datetime.date(2021, 9, 16)) Instant((2021, 9, 16)) - >>> build_instant(Instant((2021, 9, 16))) + >>> build_instant(periods.Instant((2021, 9, 16))) Instant((2021, 9, 16)) - >>> build_instant(Period(("year", Instant((2021, 9, 16)), 1))) + >>> build_instant(periods.Period(("year", periods.Instant((2021, 9, 16)), 1))) Instant((2021, 9, 16)) >>> build_instant("2021") @@ -49,23 +47,23 @@ def build_instant(value: Any) -> Optional[types.Instant]: if value is None: return None - if isinstance(value, Instant): + if isinstance(value, periods.Instant): return value if isinstance(value, str): - if not _config.INSTANT_PATTERN.match(value): + if not periods.INSTANT_PATTERN.match(value): raise ValueError( f"'{value}' is not a valid instant. Instants are described" "using the 'YYYY-MM-DD' format, for instance '2015-06-15'." ) - instant = Instant( + instant = periods.Instant( int(fragment) for fragment in value.split('-', 2)[:3] ) elif isinstance(value, datetime.date): - instant = Instant((value.year, value.month, value.day)) + instant = periods.Instant((value.year, value.month, value.day)) elif isinstance(value, int): instant = (value,) @@ -74,7 +72,7 @@ def build_instant(value: Any) -> Optional[types.Instant]: assert 1 <= len(value) <= 3 instant = tuple(value) - elif isinstance(value, Period): + elif isinstance(value, periods.Period): instant = value.start else: @@ -83,12 +81,12 @@ def build_instant(value: Any) -> Optional[types.Instant]: instant = value if len(instant) == 1: - return Instant((instant[0], 1, 1)) + return periods.Instant((instant[0], 1, 1)) if len(instant) == 2: - return Instant((instant[0], instant[1], 1)) + return periods.Instant((instant[0], instant[1], 1)) - return Instant(instant) + return periods.Instant(instant) def build_period(value: Any) -> types.Period: @@ -104,10 +102,10 @@ def build_period(value: Any) -> types.Period: :exc:`ValueError`: When the arguments were invalid, like "2021-32-13". Examples: - >>> build_period(Period(("year", Instant((2021, 1, 1)), 1))) + >>> build_period(periods.Period(("year", periods.Instant((2021, 1, 1)), 1))) Period(('year', Instant((2021, 1, 1)), 1)) - >>> build_period(Instant((2021, 1, 1))) + >>> build_period(periods.Instant((2021, 1, 1))) Period(('day', Instant((2021, 1, 1)), 1)) >>> build_period("eternity") @@ -136,17 +134,17 @@ def build_period(value: Any) -> types.Period: """ - if isinstance(value, Period): + if isinstance(value, periods.Period): return value - if isinstance(value, Instant): - return Period((_config.DAY, value, 1)) + if isinstance(value, periods.Instant): + return periods.Period((periods.DAY, value, 1)) - if value == "ETERNITY" or value == _config.ETERNITY: - return Period(("eternity", build_instant(datetime.date.min), float("inf"))) + if value == "ETERNITY" or value == periods.ETERNITY: + return periods.Period(("eternity", build_instant(datetime.date.min), float("inf"))) if isinstance(value, int): - return Period((_config.YEAR, Instant((value, 1, 1)), 1)) + return periods.Period((periods.YEAR, periods.Instant((value, 1, 1)), 1)) if not isinstance(value, str): _raise_error(value) @@ -166,7 +164,7 @@ def build_period(value: Any) -> types.Period: # Left-most component must be a valid unit unit = components[0] - if unit not in (_config.DAY, _config.MONTH, _config.YEAR): + if unit not in (periods.DAY, periods.MONTH, periods.YEAR): _raise_error(value) # Middle component must be a valid iso period @@ -195,7 +193,7 @@ def build_period(value: Any) -> types.Period: if unit_weight(base_period.unit) > unit_weight(unit): _raise_error(value) - return Period((unit, base_period.start, size)) + return periods.Period((unit, base_period.start, size)) def key_period_size(period: types.Period) -> str: @@ -210,13 +208,13 @@ def key_period_size(period: types.Period) -> str: :obj:`str`: A string. Examples: - >>> instant = Instant((2021, 9, 14)) + >>> instant = periods.Instant((2021, 9, 14)) - >>> period = Period(("day", instant, 1)) + >>> period = periods.Period(("day", instant, 1)) >>> key_period_size(period) '100_1' - >>> period = Period(("year", instant, 3)) + >>> period = periods.Period(("year", instant, 3)) >>> key_period_size(period) '300_3' @@ -259,13 +257,13 @@ def parse_simple_period(value: str) -> Optional[types.Period]: return None else: - return Period((_config.DAY, Instant((date.year, date.month, date.day)), 1)) + return periods.Period((periods.DAY, periods.Instant((date.year, date.month, date.day)), 1)) else: - return Period((_config.MONTH, Instant((date.year, date.month, 1)), 1)) + return periods.Period((periods.MONTH, periods.Instant((date.year, date.month, 1)), 1)) else: - return Period((_config.YEAR, Instant((date.year, date.month, 1)), 1)) + return periods.Period((periods.YEAR, periods.Instant((date.year, date.month, 1)), 1)) def unit_weights() -> Dict[str, int]: @@ -278,10 +276,10 @@ def unit_weights() -> Dict[str, int]: """ return { - _config.DAY: 100, - _config.MONTH: 200, - _config.YEAR: 300, - _config.ETERNITY: 400, + periods.DAY: 100, + periods.MONTH: 200, + periods.YEAR: 300, + periods.ETERNITY: 400, } diff --git a/openfisca_core/periods/_units.py b/openfisca_core/periods/_units.py new file mode 100644 index 0000000000..5b139ab0d0 --- /dev/null +++ b/openfisca_core/periods/_units.py @@ -0,0 +1,4 @@ +DAY = "day" +MONTH = "month" +YEAR = "year" +ETERNITY = "eternity" diff --git a/openfisca_core/periods/instant_.py b/openfisca_core/periods/instant_.py index be547f2c1c..f2f5cba021 100644 --- a/openfisca_core/periods/instant_.py +++ b/openfisca_core/periods/instant_.py @@ -7,7 +7,7 @@ from openfisca_core import types -from . import _config, _funcs +from .. import periods class Instant(tuple): @@ -65,28 +65,30 @@ class Instant(tuple): """ #: A cache with the ``datetime.date`` representation of the ``Instant``. - dates: Dict[Instant, datetime.date] = {} + _dates: Dict[Instant, datetime.date] = {} #: A cache with the ``str`` representation of the ``Instant``. - strings: Dict[Instant, str] = {} + _strings: Dict[Instant, str] = {} def __repr__(self) -> str: return f"{Instant.__name__}({super(Instant, self).__repr__()})" def __str__(self) -> str: - string = Instant.strings.get(self) + string = Instant._strings.get(self) - if string is None: - Instant.strings[self] = self.date.isoformat() + if string is not None: + return string - return Instant.strings[self] + Instant._strings = {self: self.date.isoformat(), **Instant._strings} + + return str(self) @staticmethod def to_date(value: Any) -> Optional[datetime.date]: """Returns the date representation of an ``Instant``. Args: - value (:any:): + value (Any): An ``instant-like`` object to get the date from. Returns: @@ -99,7 +101,7 @@ def to_date(value: Any) -> Optional[datetime.date]: """ - instant = _funcs.build_instant(value) + instant = periods.build_instant(value) if instant is None: return None @@ -168,12 +170,14 @@ def date(self) -> datetime.date: """ - date = Instant.dates.get(self) + date = Instant._dates.get(self) + + if date is not None: + return date - if date is None: - Instant.dates[self] = datetime.date(*self) + Instant._dates = {self: datetime.date(*self), **Instant._dates} - return Instant.dates[self] + return self.date def offset(self, offset: Union[str, int], unit: str) -> types.Instant: """Increments/decrements the given instant with offset units. @@ -207,28 +211,28 @@ def offset(self, offset: Union[str, int], unit: str) -> types.Instant: year, month, day = self - assert unit in (_config.DAY, _config.MONTH, _config.YEAR), 'Invalid unit: {} of type {}'.format(unit, type(unit)) + assert unit in (periods.DAY, periods.MONTH, periods.YEAR), 'Invalid unit: {} of type {}'.format(unit, type(unit)) if offset == 'first-of': - if unit == _config.MONTH: + if unit == periods.MONTH: day = 1 - elif unit == _config.YEAR: + elif unit == periods.YEAR: month = 1 day = 1 elif offset == 'last-of': - if unit == _config.MONTH: + if unit == periods.MONTH: day = calendar.monthrange(year, month)[1] - elif unit == _config.YEAR: + elif unit == periods.YEAR: month = 12 day = 31 else: assert isinstance(offset, int), 'Invalid offset: {} of type {}'.format(offset, type(offset)) - if unit == _config.DAY: + if unit == periods.DAY: day += offset if offset < 0: @@ -254,7 +258,7 @@ def offset(self, offset: Union[str, int], unit: str) -> types.Instant: day -= month_last_day month_last_day = calendar.monthrange(year, month)[1] - elif unit == _config.MONTH: + elif unit == periods.MONTH: month += offset if offset < 0: @@ -271,7 +275,7 @@ def offset(self, offset: Union[str, int], unit: str) -> types.Instant: if day > month_last_day: day = month_last_day - elif unit == _config.YEAR: + elif unit == periods.YEAR: year += offset # Handle february month of leap year. diff --git a/openfisca_core/periods/period_.py b/openfisca_core/periods/period_.py index 4f287006d1..11e2f853ea 100644 --- a/openfisca_core/periods/period_.py +++ b/openfisca_core/periods/period_.py @@ -7,8 +7,7 @@ from openfisca_core import types -from . import _config, _funcs -from .instant_ import Instant +from .. import periods class Period(tuple): @@ -29,8 +28,8 @@ class Period(tuple): The ``unit``, ``start``, and ``size``, accordingly. Examples: - >>> instant = Instant((2021, 9, 1)) - >>> period = Period((_config.YEAR, instant, 3)) + >>> instant = periods.Instant((2021, 9, 1)) + >>> period = periods.Period((periods.YEAR, instant, 3)) ``Periods`` are represented as a ``tuple`` containing the ``unit``, an ``Instant`` and the ``size``: @@ -62,10 +61,10 @@ class Period(tuple): >>> len(period) 3 - >>> period == Period(("year", instant, 3)) + >>> period == periods.Period(("year", instant, 3)) True - >>> period > Period(("year", instant, 3)) + >>> period > periods.Period(("year", instant, 3)) False >>> unit, (year, month, day), size = period @@ -80,59 +79,62 @@ def __repr__(self) -> str: def __str__(self) -> str: """Transform period to a string. + Returns: + str: A string representation of the period. + Examples: - >>> str(Period(("year", Instant((2021, 1, 1)), 1))) + >>> str(Period(("year", periods.Instant((2021, 1, 1)), 1))) '2021' - >>> str(Period(("year", Instant((2021, 2, 1)), 1))) + >>> str(Period(("year", periods.Instant((2021, 2, 1)), 1))) 'year:2021-02' - >>> str(Period(("month", Instant((2021, 2, 1)), 1))) + >>> str(Period(("month", periods.Instant((2021, 2, 1)), 1))) '2021-02' - >>> str(Period(("year", Instant((2021, 1, 1)), 2))) + >>> str(Period(("year", periods.Instant((2021, 1, 1)), 2))) 'year:2021:2' - >>> str(Period(("month", Instant((2021, 1, 1)), 2))) + >>> str(Period(("month", periods.Instant((2021, 1, 1)), 2))) 'month:2021-01:2' - >>> str(Period(("month", Instant((2021, 1, 1)), 12))) + >>> str(Period(("month", periods.Instant((2021, 1, 1)), 12))) '2021' - >>> str(Period(("year", Instant((2021, 3, 1)), 2))) + >>> str(Period(("year", periods.Instant((2021, 3, 1)), 2))) 'year:2021-03:2' - >>> str(Period(("month", Instant((2021, 3, 1)), 2))) + >>> str(Period(("month", periods.Instant((2021, 3, 1)), 2))) 'month:2021-03:2' - >>> str(Period(("month", Instant((2021, 3, 1)), 12))) + >>> str(Period(("month", periods.Instant((2021, 3, 1)), 12))) 'year:2021-03' """ unit, start_instant, size = self - if unit == _config.ETERNITY: + if unit == periods.ETERNITY: return "ETERNITY" year, month, day = start_instant # 1 year long period - if (unit == _config.MONTH and size == 12 or unit == _config.YEAR and size == 1): + if (unit == periods.MONTH and size == 12 or unit == periods.YEAR and size == 1): if month == 1: # civil year starting from january return str(year) else: # rolling year - return '{}:{}-{:02d}'.format(_config.YEAR, year, month) + return '{}:{}-{:02d}'.format(periods.YEAR, year, month) # simple month - if unit == _config.MONTH and size == 1: + if unit == periods.MONTH and size == 1: return '{}-{:02d}'.format(year, month) # several civil years - if unit == _config.YEAR and month == 1: + if unit == periods.YEAR and month == 1: return '{}:{}:{}'.format(unit, year, size) - if unit == _config.DAY: + if unit == periods.DAY: if size == 1: return '{}-{:02d}-{:02d}'.format(year, month, day) else: @@ -148,13 +150,16 @@ def date(self) -> datetime.date: Returns: A datetime.date. + Raises: + ValueError: If the period's size is greater than 1. + Examples: - >>> instant = Instant((2021, 10, 1)) - >>> period = Period((_config.YEAR, instant, 1)) + >>> instant = periods.Instant((2021, 10, 1)) + >>> period = periods.Period((periods.YEAR, instant, 1)) >>> period.date datetime.date(2021, 10, 1) - >>> period = Period((_config.YEAR, instant, 3)) + >>> period = periods.Period((periods.YEAR, instant, 3)) >>> period.date Traceback (most recent call last): ValueError: "date" is undefined for a period of size > 1: year:2021-10:3. @@ -174,8 +179,8 @@ def unit(self) -> str: An int. Example: - >>> instant = Instant((2021, 10, 1)) - >>> period = Period((_config.YEAR, instant, 3)) + >>> instant = periods.Instant((2021, 10, 1)) + >>> period = periods.Period((periods.YEAR, instant, 3)) >>> period.unit 'year' @@ -191,12 +196,12 @@ def days(self) -> int: An int. Examples: - >>> instant = Instant((2021, 10, 1)) - >>> period = Period((_config.YEAR, instant, 3)) + >>> instant = periods.Instant((2021, 10, 1)) + >>> period = periods.Period((periods.YEAR, instant, 3)) >>> period.size_in_days 1096 - >>> period = Period((_config.MONTH, instant, 3)) + >>> period = periods.Period((periods.MONTH, instant, 3)) >>> period.size_in_days 92 @@ -212,8 +217,8 @@ def size(self) -> int: An int. Example: - >>> instant = Instant((2021, 10, 1)) - >>> period = Period((_config.YEAR, instant, 3)) + >>> instant = periods.Instant((2021, 10, 1)) + >>> period = periods.Period((periods.YEAR, instant, 3)) >>> period.size 3 @@ -228,23 +233,26 @@ def size_in_months(self) -> int: Returns: An int. + Raises: + ValueError: If the period's unit is not a month or a year. + Examples: - >>> instant = Instant((2021, 10, 1)) - >>> period = Period((_config.YEAR, instant, 3)) + >>> instant = periods.Instant((2021, 10, 1)) + >>> period = periods.Period((periods.YEAR, instant, 3)) >>> period.size_in_months 36 - >>> period = Period((_config.DAY, instant, 3)) + >>> period = periods.Period((periods.DAY, instant, 3)) >>> period.size_in_months Traceback (most recent call last): ValueError: Cannot calculate number of months in day. """ - if (self[0] == _config.MONTH): + if (self[0] == periods.MONTH): return self[2] - if(self[0] == _config.YEAR): + if(self[0] == periods.YEAR): return self[2] * 12 raise ValueError(f"Cannot calculate number of months in {self[0]}.") @@ -253,13 +261,19 @@ def size_in_months(self) -> int: def size_in_days(self) -> int: """The ``size`` of the ``Period`` in days. + Returns: + An int. + + Raises: + ValueError: If the period's unit is not a day, a month or a year. + Examples: - >>> instant = Instant((2019, 10, 1)) - >>> period = Period((_config.YEAR, instant, 3)) + >>> instant = periods.Instant((2019, 10, 1)) + >>> period = periods.Period((periods.YEAR, instant, 3)) >>> period.size_in_days 1096 - >>> period = Period((_config.MONTH, instant, 3)) + >>> period = periods.Period((periods.MONTH, instant, 3)) >>> period.size_in_days 92 @@ -267,11 +281,11 @@ def size_in_days(self) -> int: unit, instant, length = self - if unit == _config.DAY: + if unit == periods.DAY: return length - if unit in [_config.MONTH, _config.YEAR]: - last_day = self.start.offset(length, unit).offset(-1, _config.DAY) + if unit in [periods.MONTH, periods.YEAR]: + last_day = self.start.offset(length, unit).offset(-1, periods.DAY) return (last_day.date - self.start.date).days + 1 raise ValueError(f"Cannot calculate number of days in {unit}") @@ -284,8 +298,8 @@ def start(self) -> types.Instant: An Instant. Example: - >>> instant = Instant((2021, 10, 1)) - >>> period = Period((_config.YEAR, instant, 3)) + >>> instant = periods.Instant((2021, 10, 1)) + >>> period = periods.Period((periods.YEAR, instant, 3)) >>> period.start Instant((2021, 10, 1)) @@ -301,13 +315,13 @@ def stop(self) -> types.Instant: An Instant. Examples: - >>> Period(("year", Instant((2012, 2, 29)), 1)).stop + >>> periods.Period(("year", periods.Instant((2012, 2, 29)), 1)).stop Instant((2013, 2, 28)) - >>> Period(("month", Instant((2012, 2, 29)), 1)).stop + >>> periods.Period(("month", periods.Instant((2012, 2, 29)), 1)).stop Instant((2012, 3, 28)) - >>> Period(("day", Instant((2012, 2, 29)), 1)).stop + >>> periods.Period(("day", periods.Instant((2012, 2, 29)), 1)).stop Instant((2012, 2, 29)) """ @@ -315,8 +329,8 @@ def stop(self) -> types.Instant: unit, start_instant, size = self year, month, day = start_instant - if unit == _config.ETERNITY: - return Instant((float("inf"), float("inf"), float("inf"))) + if unit == periods.ETERNITY: + return periods.Instant((float("inf"), float("inf"), float("inf"))) if unit == 'day': if size > 1: @@ -355,66 +369,117 @@ def stop(self) -> types.Instant: month = 1 day -= month_last_day - return Instant((year, month, day)) + return periods.Instant((year, month, day)) @property def last_month(self) -> types.Period: + """Last month of the ``Period``. + + Returns: + A Period. + + """ + return self.first_month.offset(-1) @property def last_3_months(self) -> types.Period: + """Last 3 months of the ``Period``. + + Returns: + A Period. + + """ + start: types.Instant = self.first_month.start - return self.__class__((_config.MONTH, start, 3)).offset(-3) + return self.__class__((periods.MONTH, start, 3)).offset(-3) @property def last_year(self) -> types.Period: - start: types.Instant = self.start.offset("first-of", _config.YEAR) - return self.__class__((_config.YEAR, start, 1)).offset(-1) + """Last year of the ``Period``.""" + start: types.Instant = self.start.offset("first-of", periods.YEAR) + return self.__class__((periods.YEAR, start, 1)).offset(-1) @property def n_2(self) -> types.Period: - start: types.Instant = self.start.offset("first-of", _config.YEAR) - return self.__class__((_config.YEAR, start, 1)).offset(-2) + """Last 2 years of the ``Period``. + + Returns: + A Period. + + """ + + start: types.Instant = self.start.offset("first-of", periods.YEAR) + return self.__class__((periods.YEAR, start, 1)).offset(-2) @property def this_year(self) -> types.Period: - start: types.Instant = self.start.offset("first-of", _config.YEAR) - return self.__class__((_config.YEAR, start, 1)) + """A new year ``Period`` starting at the beginning of the year. + + Returns: + A Period. + + """ + + start: types.Instant = self.start.offset("first-of", periods.YEAR) + return self.__class__((periods.YEAR, start, 1)) @property def first_month(self) -> types.Period: - start: types.Instant = self.start.offset("first-of", _config.MONTH) - return self.__class__((_config.MONTH, start, 1)) + """A new month ``Period`` starting at the first of the month. + + Returns: + A Period. + + """ + + start: types.Instant = self.start.offset("first-of", periods.MONTH) + return self.__class__((periods.MONTH, start, 1)) @property def first_day(self) -> types.Period: - return self.__class__((_config.DAY, self.start, 1)) + """A new day ``Period``. + + Returns: + A Period. + + """ + return self.__class__((periods.DAY, self.start, 1)) def get_subperiods(self, unit: str) -> Sequence[types.Period]: """Return the list of all the periods of unit ``unit``. + Args: + unit: A string representing period's ``unit``. + + Returns: + A list of Periods. + + Raises: + ValueError: If the period's unit is smaller than the given unit. + Examples: - >>> period = Period((_config.YEAR, Instant((2021, 1, 1)), 1)) - >>> period.get_subperiods(_config.MONTH) + >>> period = periods.Period((periods.YEAR, periods.Instant((2021, 1, 1)), 1)) + >>> period.get_subperiods(periods.MONTH) [Period(('month', Instant((2021, 1, 1)), 1)),...2021, 12, 1)), 1))] - >>> period = Period((_config.YEAR, Instant((2021, 1, 1)), 2)) - >>> period.get_subperiods(_config.YEAR) + >>> period = periods.Period((periods.YEAR, periods.Instant((2021, 1, 1)), 2)) + >>> period.get_subperiods(periods.YEAR) [Period(('year', Instant((2021, 1, 1)), 1)),...((2022, 1, 1)), 1))] """ - if _funcs.unit_weight(self.unit) < _funcs.unit_weight(unit): + if periods.unit_weight(self.unit) < periods.unit_weight(unit): raise ValueError('Cannot subdivide {0} into {1}'.format(self.unit, unit)) - if unit == _config.YEAR: - return [self.this_year.offset(i, _config.YEAR) for i in range(self.size)] + if unit == periods.YEAR: + return [self.this_year.offset(i, periods.YEAR) for i in range(self.size)] - if unit == _config.MONTH: - return [self.first_month.offset(i, _config.MONTH) for i in range(self.size_in_months)] + if unit == periods.MONTH: + return [self.first_month.offset(i, periods.MONTH) for i in range(self.size_in_months)] - if unit == _config.DAY: - return [self.first_day.offset(i, _config.DAY) for i in range(self.size_in_days)] + if unit == periods.DAY: + return [self.first_day.offset(i, periods.DAY) for i in range(self.size_in_days)] def offset( self, @@ -431,16 +496,16 @@ def offset( Period: A new one. Examples: - >>> Period(("day", Instant((2014, 2, 3)), 1)).offset("first-of", "month") + >>> periods.Period(("day", periods.Instant((2014, 2, 3)), 1)).offset("first-of", "month") Period(('day', Instant((2014, 2, 1)), 1)) - >>> Period(("month", Instant((2014, 2, 3)), 4)).offset("last-of", "month") + >>> periods.Period(("month", periods.Instant((2014, 2, 3)), 4)).offset("last-of", "month") Period(('month', Instant((2014, 2, 28)), 4)) - >>> Period(("day", Instant((2021, 1, 1)), 365)).offset(-3) + >>> periods.Period(("day", periods.Instant((2021, 1, 1)), 365)).offset(-3) Period(('day', Instant((2020, 12, 29)), 365)) - >>> Period(("day", Instant((2021, 1, 1)), 365)).offset(1, "year") + >>> periods.Period(("day", periods.Instant((2021, 1, 1)), 365)).offset(1, "year") Period(('day', Instant((2022, 1, 1)), 365)) """ @@ -453,12 +518,12 @@ def contains(self, other: types.Period) -> bool: Args: other (:obj:`.Period`): The other ``Period``. - Returns + Returns: True if ``other`` is contained, otherwise False. Example: - >>> period = Period((_config.YEAR, Instant((2021, 1, 1)), 1)) - >>> sub_period = Period((_config.MONTH, Instant((2021, 1, 1)), 3)) + >>> period = periods.Period((periods.YEAR, periods.Instant((2021, 1, 1)), 1)) + >>> sub_period = periods.Period((periods.MONTH, periods.Instant((2021, 1, 1)), 3)) >>> period.contains(sub_period) True diff --git a/openfisca_core/periods/py.typed b/openfisca_core/periods/py.typed new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openfisca_core/types/_domain.py b/openfisca_core/types/_domain.py index 3c64682d08..951e752b1c 100644 --- a/openfisca_core/types/_domain.py +++ b/openfisca_core/types/_domain.py @@ -58,6 +58,11 @@ def get_memory_usage(self) -> Any: class Instant(Protocol): """Instant protocol.""" + @property + @abc.abstractmethod + def date(self) -> Any: + """Abstract method.""" + @abc.abstractmethod def offset(self, offset: Any, unit: Any) -> Any: """Abstract method.""" From 3c667e3c85d986fc0c2df632b0606220d967ce62 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Wed, 14 Dec 2022 05:15:31 +0100 Subject: [PATCH 55/70] Fix circular dependencies --- CHANGELOG.md | 10 +- openfisca_core/periods/__init__.py | 18 ++- openfisca_core/periods/_config.py | 5 - openfisca_core/periods/_errors.py | 64 ++++++++ openfisca_core/periods/_funcs.py | 144 ++++++----------- openfisca_core/periods/_units.py | 7 + openfisca_core/periods/instant_.py | 44 +++--- openfisca_core/periods/period_.py | 153 ++++++++++--------- openfisca_core/periods/tests/test_helpers.py | 8 + openfisca_core/periods/tests/test_instant.py | 7 +- openfisca_core/periods/tests/test_period.py | 16 ++ openfisca_core/simulations/simulation.py | 2 +- openfisca_core/types/_domain.py | 1 + setup.cfg | 2 +- tests/web_api/test_calculate.py | 6 +- 15 files changed, 277 insertions(+), 210 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 14c094800a..afd5ba81f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,13 +5,17 @@ #### Breaking changes - Deprecate `periods.intersect`. -- Rename `instant` to `build_instant` -- Rename `period` to `build_period` -- Move `instant_date` to `Instant.to_date` +- 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`. #### 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) diff --git a/openfisca_core/periods/__init__.py b/openfisca_core/periods/__init__.py index a4402787dc..3ba24cf9db 100644 --- a/openfisca_core/periods/__init__.py +++ b/openfisca_core/periods/__init__.py @@ -28,20 +28,26 @@ """ from ._config import ( # noqa: F401 - DAY, - MONTH, - YEAR, - ETERNITY, INSTANT_PATTERN, ) +from ._errors import ( # noqa: F401 + InstantTypeError, + ) + from ._funcs import ( # noqa: F401 build_instant, build_period, key_period_size, parse_simple_period, - unit_weight, - unit_weights, + ) + +from ._units import ( # noqa: F401 + DAY, + ETERNITY, + MONTH, + YEAR, + UNIT_WEIGHTS, ) from .instant_ import Instant # noqa: F401 diff --git a/openfisca_core/periods/_config.py b/openfisca_core/periods/_config.py index 68dc243e10..485f5fd44a 100644 --- a/openfisca_core/periods/_config.py +++ b/openfisca_core/periods/_config.py @@ -4,11 +4,6 @@ import re -DAY = "day" -MONTH = "month" -YEAR = "year" -ETERNITY = "eternity" - # Matches "2015", "2015-01", "2015-01-01" # Does not match "2015-13", "2015-12-32" INSTANT_PATTERN: Pattern = re.compile(r"^\d{4}(-(0[1-9]|1[012]))?(-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01]))?$") diff --git a/openfisca_core/periods/_errors.py b/openfisca_core/periods/_errors.py index e69de29bb2..b412fffda0 100644 --- a/openfisca_core/periods/_errors.py +++ b/openfisca_core/periods/_errors.py @@ -0,0 +1,64 @@ +from __future__ import annotations + +from typing import Any + +from openfisca_core import types + +from ._units import DAY, MONTH, YEAR + + +LEARN_MORE = ( + "Learn more about legal period formats in OpenFisca: " + "." + ) + + +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(types.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}" + ) diff --git a/openfisca_core/periods/_funcs.py b/openfisca_core/periods/_funcs.py index 4002c1e10e..aec62e0519 100644 --- a/openfisca_core/periods/_funcs.py +++ b/openfisca_core/periods/_funcs.py @@ -1,13 +1,16 @@ from __future__ import annotations -from typing import Any, Dict, NoReturn, Optional +from typing import Any, Optional import datetime -import os from openfisca_core import types -from .. import periods +from ._config import INSTANT_PATTERN +from ._errors import InstantFormatError, PeriodFormatError +from ._units import DAY, ETERNITY, MONTH, YEAR, UNIT_WEIGHTS +from .instant_ import Instant +from .period_ import Period def build_instant(value: Any) -> Optional[types.Instant]: @@ -21,16 +24,16 @@ def build_instant(value: Any) -> Optional[types.Instant]: :obj:`.Instant`: Otherwise. Raises: - ValueError: When the arguments were invalid, like "2021-32-13". + InstantFormatError: When the arguments were invalid, like "2021-32-13". Examples: >>> build_instant(datetime.date(2021, 9, 16)) Instant((2021, 9, 16)) - >>> build_instant(periods.Instant((2021, 9, 16))) + >>> build_instant(Instant((2021, 9, 16))) Instant((2021, 9, 16)) - >>> build_instant(periods.Period(("year", periods.Instant((2021, 9, 16)), 1))) + >>> build_instant(Period(("year", Instant((2021, 9, 16)), 1))) Instant((2021, 9, 16)) >>> build_instant("2021") @@ -47,23 +50,20 @@ def build_instant(value: Any) -> Optional[types.Instant]: if value is None: return None - if isinstance(value, periods.Instant): + if isinstance(value, Instant): return value if isinstance(value, str): - if not periods.INSTANT_PATTERN.match(value): - raise ValueError( - f"'{value}' is not a valid instant. Instants are described" - "using the 'YYYY-MM-DD' format, for instance '2015-06-15'." - ) + if not INSTANT_PATTERN.match(value): + raise InstantFormatError(value) - instant = periods.Instant( + instant = Instant( int(fragment) for fragment in value.split('-', 2)[:3] ) elif isinstance(value, datetime.date): - instant = periods.Instant((value.year, value.month, value.day)) + instant = Instant((value.year, value.month, value.day)) elif isinstance(value, int): instant = (value,) @@ -72,7 +72,7 @@ def build_instant(value: Any) -> Optional[types.Instant]: assert 1 <= len(value) <= 3 instant = tuple(value) - elif isinstance(value, periods.Period): + elif isinstance(value, Period): instant = value.start else: @@ -81,12 +81,12 @@ def build_instant(value: Any) -> Optional[types.Instant]: instant = value if len(instant) == 1: - return periods.Instant((instant[0], 1, 1)) + return Instant((instant[0], 1, 1)) if len(instant) == 2: - return periods.Instant((instant[0], instant[1], 1)) + return Instant((instant[0], instant[1], 1)) - return periods.Instant(instant) + return Instant(instant) def build_period(value: Any) -> types.Period: @@ -99,13 +99,13 @@ def build_period(value: Any) -> types.Period: :obj:`.Period`: A period. Raises: - :exc:`ValueError`: When the arguments were invalid, like "2021-32-13". + PeriodFormatError: When the arguments were invalid, like "2021-32-13". Examples: - >>> build_period(periods.Period(("year", periods.Instant((2021, 1, 1)), 1))) + >>> build_period(Period(("year", Instant((2021, 1, 1)), 1))) Period(('year', Instant((2021, 1, 1)), 1)) - >>> build_period(periods.Instant((2021, 1, 1))) + >>> build_period(Instant((2021, 1, 1))) Period(('day', Instant((2021, 1, 1)), 1)) >>> build_period("eternity") @@ -134,20 +134,20 @@ def build_period(value: Any) -> types.Period: """ - if isinstance(value, periods.Period): + if isinstance(value, Period): return value - if isinstance(value, periods.Instant): - return periods.Period((periods.DAY, value, 1)) + if isinstance(value, Instant): + return Period((DAY, value, 1)) - if value == "ETERNITY" or value == periods.ETERNITY: - return periods.Period(("eternity", build_instant(datetime.date.min), float("inf"))) + if value == "ETERNITY" or value == ETERNITY: + return Period(("eternity", build_instant(datetime.date.min), float("inf"))) if isinstance(value, int): - return periods.Period((periods.YEAR, periods.Instant((value, 1, 1)), 1)) + return Period((YEAR, Instant((value, 1, 1)), 1)) if not isinstance(value, str): - _raise_error(value) + raise PeriodFormatError(value) # Try to parse as a simple period period = parse_simple_period(value) @@ -157,21 +157,21 @@ def build_period(value: Any) -> types.Period: # Complex periods must have a ':' in their strings if ":" not in value: - _raise_error(value) + raise PeriodFormatError(value) components = value.split(":") # Left-most component must be a valid unit unit = components[0] - if unit not in (periods.DAY, periods.MONTH, periods.YEAR): - _raise_error(value) + if unit not in (DAY, MONTH, YEAR): + raise PeriodFormatError(value) # Middle component must be a valid iso period base_period = parse_simple_period(components[1]) if not base_period: - _raise_error(value) + raise PeriodFormatError(value) # Periods like year:2015-03 have a size of 1 if len(components) == 2: @@ -183,17 +183,17 @@ def build_period(value: Any) -> types.Period: size = int(components[2]) except ValueError: - _raise_error(value) + raise PeriodFormatError(value) # If there are more than 2 ":" in the string, the period is invalid else: - _raise_error(value) + raise PeriodFormatError(value) # Reject ambiguous periods such as month:2014 - if unit_weight(base_period.unit) > unit_weight(unit): - _raise_error(value) + if UNIT_WEIGHTS[base_period.unit] > UNIT_WEIGHTS[unit]: + raise PeriodFormatError(value) - return periods.Period((unit, base_period.start, size)) + return Period((unit, base_period.start, size)) def key_period_size(period: types.Period) -> str: @@ -208,13 +208,13 @@ def key_period_size(period: types.Period) -> str: :obj:`str`: A string. Examples: - >>> instant = periods.Instant((2021, 9, 14)) + >>> instant = Instant((2021, 9, 14)) - >>> period = periods.Period(("day", instant, 1)) + >>> period = Period(("day", instant, 1)) >>> key_period_size(period) '100_1' - >>> period = periods.Period(("year", instant, 3)) + >>> period = Period(("year", instant, 3)) >>> key_period_size(period) '300_3' @@ -222,13 +222,17 @@ def key_period_size(period: types.Period) -> str: unit, start, size = period - return f"{unit_weight(unit)}_{size}" + return f"{UNIT_WEIGHTS[unit]}_{size}" def parse_simple_period(value: str) -> Optional[types.Period]: """Parse simple periods respecting the ISO format. - Such as "2012" or "2015-03". + Args: + value: A string such as such as "2012" or "2015-03". + + Returns: + A Period. Examples: >>> parse_simple_period("2022") @@ -257,60 +261,10 @@ def parse_simple_period(value: str) -> Optional[types.Period]: return None else: - return periods.Period((periods.DAY, periods.Instant((date.year, date.month, date.day)), 1)) + return Period((DAY, Instant((date.year, date.month, date.day)), 1)) else: - return periods.Period((periods.MONTH, periods.Instant((date.year, date.month, 1)), 1)) + return Period((MONTH, Instant((date.year, date.month, 1)), 1)) else: - return periods.Period((periods.YEAR, periods.Instant((date.year, date.month, 1)), 1)) - - -def unit_weights() -> Dict[str, int]: - """Assign weights to date units. - - Examples: - >>> unit_weights() - {'day': 100, ...} - - """ - - return { - periods.DAY: 100, - periods.MONTH: 200, - periods.YEAR: 300, - periods.ETERNITY: 400, - } - - -def unit_weight(unit: str) -> int: - """Retrieves a specific date unit weight. - - Examples: - >>> unit_weight("day") - 100 - - """ - - return unit_weights()[unit] - - -def _raise_error(value: str) -> NoReturn: - """Raise an error. - - Examples: - >>> _raise_error("Oi mate!") - Traceback (most recent call last): - ValueError: Expected a period (eg. '2017', '2017-01', '2017-01-01', ...); got: - 'Oi mate!'. Learn more about legal period formats in OpenFisca: - . - - """ - - message = os.linesep.join([ - "Expected a period (eg. '2017', '2017-01', '2017-01-01', ...); got:", - f"'{value}'. Learn more about legal period formats in OpenFisca:", - "." - ]) - - raise ValueError(message) + return Period((YEAR, Instant((date.year, date.month, 1)), 1)) diff --git a/openfisca_core/periods/_units.py b/openfisca_core/periods/_units.py index 5b139ab0d0..fc243a6572 100644 --- a/openfisca_core/periods/_units.py +++ b/openfisca_core/periods/_units.py @@ -2,3 +2,10 @@ MONTH = "month" YEAR = "year" ETERNITY = "eternity" + +UNIT_WEIGHTS = { + DAY: 100, + MONTH: 200, + YEAR: 300, + ETERNITY: 400, + } diff --git a/openfisca_core/periods/instant_.py b/openfisca_core/periods/instant_.py index f2f5cba021..0e8eb8dd2f 100644 --- a/openfisca_core/periods/instant_.py +++ b/openfisca_core/periods/instant_.py @@ -1,13 +1,14 @@ from __future__ import annotations -from typing import Any, Dict, Optional, Union +from typing import Dict, Optional, Union import calendar import datetime from openfisca_core import types -from .. import periods +from ._errors import DateUnitValueError, InstantTypeError, OffsetTypeError +from ._units import DAY, MONTH, YEAR class Instant(tuple): @@ -84,7 +85,7 @@ def __str__(self) -> str: return str(self) @staticmethod - def to_date(value: Any) -> Optional[datetime.date]: + def to_date(value: Optional[types.Instant]) -> Optional[datetime.date]: """Returns the date representation of an ``Instant``. Args: @@ -95,18 +96,22 @@ def to_date(value: Any) -> Optional[datetime.date]: None: When ``value`` is None. datetime.date: Otherwise. + Raises: + InstantTypeError: When ``value`` is not an ``Instant``. + Examples: >>> Instant.to_date(Instant((2021, 1, 1))) datetime.date(2021, 1, 1) """ - instant = periods.build_instant(value) - - if instant is None: + if value is None: return None - return instant.date + if isinstance(value, types.Instant): + return value.date + + raise InstantTypeError(value) @property def year(self) -> int: @@ -190,9 +195,8 @@ def offset(self, offset: Union[str, int], unit: str) -> types.Instant: Instant: A new one. Raises: - AssertionError: When ``unit`` is not a date unit. - AssertionError: When ``offset`` is not either ``first-of``, - ``last-of``, or any ``int``. + DateUnitValueError: When ``unit`` is not a date unit. + OffsetTypeError: When ``offset`` is of type ``int``. Examples: >>> Instant((2020, 12, 31)).offset("first-of", "month") @@ -211,28 +215,30 @@ def offset(self, offset: Union[str, int], unit: str) -> types.Instant: year, month, day = self - assert unit in (periods.DAY, periods.MONTH, periods.YEAR), 'Invalid unit: {} of type {}'.format(unit, type(unit)) + if unit not in (DAY, MONTH, YEAR): + raise DateUnitValueError(unit) if offset == 'first-of': - if unit == periods.MONTH: + if unit == MONTH: day = 1 - elif unit == periods.YEAR: + elif unit == YEAR: month = 1 day = 1 elif offset == 'last-of': - if unit == periods.MONTH: + if unit == MONTH: day = calendar.monthrange(year, month)[1] - elif unit == periods.YEAR: + elif unit == YEAR: month = 12 day = 31 else: - assert isinstance(offset, int), 'Invalid offset: {} of type {}'.format(offset, type(offset)) + if not isinstance(offset, int): + raise OffsetTypeError(offset) - if unit == periods.DAY: + if unit == DAY: day += offset if offset < 0: @@ -258,7 +264,7 @@ def offset(self, offset: Union[str, int], unit: str) -> types.Instant: day -= month_last_day month_last_day = calendar.monthrange(year, month)[1] - elif unit == periods.MONTH: + elif unit == MONTH: month += offset if offset < 0: @@ -275,7 +281,7 @@ def offset(self, offset: Union[str, int], unit: str) -> types.Instant: if day > month_last_day: day = month_last_day - elif unit == periods.YEAR: + elif unit == YEAR: year += offset # Handle february month of leap year. diff --git a/openfisca_core/periods/period_.py b/openfisca_core/periods/period_.py index 11e2f853ea..060fbfd201 100644 --- a/openfisca_core/periods/period_.py +++ b/openfisca_core/periods/period_.py @@ -7,7 +7,8 @@ from openfisca_core import types -from .. import periods +from ._units import DAY, MONTH, YEAR, ETERNITY, UNIT_WEIGHTS +from .instant_ import Instant class Period(tuple): @@ -28,8 +29,8 @@ class Period(tuple): The ``unit``, ``start``, and ``size``, accordingly. Examples: - >>> instant = periods.Instant((2021, 9, 1)) - >>> period = periods.Period((periods.YEAR, instant, 3)) + >>> instant = Instant((2021, 9, 1)) + >>> period = Period((YEAR, instant, 3)) ``Periods`` are represented as a ``tuple`` containing the ``unit``, an ``Instant`` and the ``size``: @@ -61,10 +62,10 @@ class Period(tuple): >>> len(period) 3 - >>> period == periods.Period(("year", instant, 3)) + >>> period == Period(("year", instant, 3)) True - >>> period > periods.Period(("year", instant, 3)) + >>> period > Period(("year", instant, 3)) False >>> unit, (year, month, day), size = period @@ -83,58 +84,58 @@ def __str__(self) -> str: str: A string representation of the period. Examples: - >>> str(Period(("year", periods.Instant((2021, 1, 1)), 1))) + >>> str(Period(("year", Instant((2021, 1, 1)), 1))) '2021' - >>> str(Period(("year", periods.Instant((2021, 2, 1)), 1))) + >>> str(Period(("year", Instant((2021, 2, 1)), 1))) 'year:2021-02' - >>> str(Period(("month", periods.Instant((2021, 2, 1)), 1))) + >>> str(Period(("month", Instant((2021, 2, 1)), 1))) '2021-02' - >>> str(Period(("year", periods.Instant((2021, 1, 1)), 2))) + >>> str(Period(("year", Instant((2021, 1, 1)), 2))) 'year:2021:2' - >>> str(Period(("month", periods.Instant((2021, 1, 1)), 2))) + >>> str(Period(("month", Instant((2021, 1, 1)), 2))) 'month:2021-01:2' - >>> str(Period(("month", periods.Instant((2021, 1, 1)), 12))) + >>> str(Period(("month", Instant((2021, 1, 1)), 12))) '2021' - >>> str(Period(("year", periods.Instant((2021, 3, 1)), 2))) + >>> str(Period(("year", Instant((2021, 3, 1)), 2))) 'year:2021-03:2' - >>> str(Period(("month", periods.Instant((2021, 3, 1)), 2))) + >>> str(Period(("month", Instant((2021, 3, 1)), 2))) 'month:2021-03:2' - >>> str(Period(("month", periods.Instant((2021, 3, 1)), 12))) + >>> str(Period(("month", Instant((2021, 3, 1)), 12))) 'year:2021-03' """ unit, start_instant, size = self - if unit == periods.ETERNITY: + if unit == ETERNITY: return "ETERNITY" year, month, day = start_instant # 1 year long period - if (unit == periods.MONTH and size == 12 or unit == periods.YEAR and size == 1): + if (unit == MONTH and size == 12 or unit == YEAR and size == 1): if month == 1: # civil year starting from january return str(year) else: # rolling year - return '{}:{}-{:02d}'.format(periods.YEAR, year, month) + return '{}:{}-{:02d}'.format(YEAR, year, month) # simple month - if unit == periods.MONTH and size == 1: + if unit == MONTH and size == 1: return '{}-{:02d}'.format(year, month) # several civil years - if unit == periods.YEAR and month == 1: + if unit == YEAR and month == 1: return '{}:{}:{}'.format(unit, year, size) - if unit == periods.DAY: + if unit == DAY: if size == 1: return '{}-{:02d}-{:02d}'.format(year, month, day) else: @@ -154,12 +155,12 @@ def date(self) -> datetime.date: ValueError: If the period's size is greater than 1. Examples: - >>> instant = periods.Instant((2021, 10, 1)) - >>> period = periods.Period((periods.YEAR, instant, 1)) + >>> instant = Instant((2021, 10, 1)) + >>> period = Period((YEAR, instant, 1)) >>> period.date datetime.date(2021, 10, 1) - >>> period = periods.Period((periods.YEAR, instant, 3)) + >>> period = Period((YEAR, instant, 3)) >>> period.date Traceback (most recent call last): ValueError: "date" is undefined for a period of size > 1: year:2021-10:3. @@ -179,8 +180,8 @@ def unit(self) -> str: An int. Example: - >>> instant = periods.Instant((2021, 10, 1)) - >>> period = periods.Period((periods.YEAR, instant, 3)) + >>> instant = Instant((2021, 10, 1)) + >>> period = Period((YEAR, instant, 3)) >>> period.unit 'year' @@ -196,12 +197,12 @@ def days(self) -> int: An int. Examples: - >>> instant = periods.Instant((2021, 10, 1)) - >>> period = periods.Period((periods.YEAR, instant, 3)) + >>> instant = Instant((2021, 10, 1)) + >>> period = Period((YEAR, instant, 3)) >>> period.size_in_days 1096 - >>> period = periods.Period((periods.MONTH, instant, 3)) + >>> period = Period((MONTH, instant, 3)) >>> period.size_in_days 92 @@ -217,8 +218,8 @@ def size(self) -> int: An int. Example: - >>> instant = periods.Instant((2021, 10, 1)) - >>> period = periods.Period((periods.YEAR, instant, 3)) + >>> instant = Instant((2021, 10, 1)) + >>> period = Period((YEAR, instant, 3)) >>> period.size 3 @@ -237,22 +238,22 @@ def size_in_months(self) -> int: ValueError: If the period's unit is not a month or a year. Examples: - >>> instant = periods.Instant((2021, 10, 1)) - >>> period = periods.Period((periods.YEAR, instant, 3)) + >>> instant = Instant((2021, 10, 1)) + >>> period = Period((YEAR, instant, 3)) >>> period.size_in_months 36 - >>> period = periods.Period((periods.DAY, instant, 3)) + >>> period = Period((DAY, instant, 3)) >>> period.size_in_months Traceback (most recent call last): ValueError: Cannot calculate number of months in day. """ - if (self[0] == periods.MONTH): + if (self[0] == MONTH): return self[2] - if(self[0] == periods.YEAR): + if(self[0] == YEAR): return self[2] * 12 raise ValueError(f"Cannot calculate number of months in {self[0]}.") @@ -268,12 +269,12 @@ def size_in_days(self) -> int: ValueError: If the period's unit is not a day, a month or a year. Examples: - >>> instant = periods.Instant((2019, 10, 1)) - >>> period = periods.Period((periods.YEAR, instant, 3)) + >>> instant = Instant((2019, 10, 1)) + >>> period = Period((YEAR, instant, 3)) >>> period.size_in_days 1096 - >>> period = periods.Period((periods.MONTH, instant, 3)) + >>> period = Period((MONTH, instant, 3)) >>> period.size_in_days 92 @@ -281,11 +282,11 @@ def size_in_days(self) -> int: unit, instant, length = self - if unit == periods.DAY: + if unit == DAY: return length - if unit in [periods.MONTH, periods.YEAR]: - last_day = self.start.offset(length, unit).offset(-1, periods.DAY) + if unit in [MONTH, YEAR]: + last_day = self.start.offset(length, unit).offset(-1, DAY) return (last_day.date - self.start.date).days + 1 raise ValueError(f"Cannot calculate number of days in {unit}") @@ -298,8 +299,8 @@ def start(self) -> types.Instant: An Instant. Example: - >>> instant = periods.Instant((2021, 10, 1)) - >>> period = periods.Period((periods.YEAR, instant, 3)) + >>> instant = Instant((2021, 10, 1)) + >>> period = Period((YEAR, instant, 3)) >>> period.start Instant((2021, 10, 1)) @@ -315,13 +316,13 @@ def stop(self) -> types.Instant: An Instant. Examples: - >>> periods.Period(("year", periods.Instant((2012, 2, 29)), 1)).stop + >>> Period(("year", Instant((2012, 2, 29)), 1)).stop Instant((2013, 2, 28)) - >>> periods.Period(("month", periods.Instant((2012, 2, 29)), 1)).stop + >>> Period(("month", Instant((2012, 2, 29)), 1)).stop Instant((2012, 3, 28)) - >>> periods.Period(("day", periods.Instant((2012, 2, 29)), 1)).stop + >>> Period(("day", Instant((2012, 2, 29)), 1)).stop Instant((2012, 2, 29)) """ @@ -329,8 +330,8 @@ def stop(self) -> types.Instant: unit, start_instant, size = self year, month, day = start_instant - if unit == periods.ETERNITY: - return periods.Instant((float("inf"), float("inf"), float("inf"))) + if unit == ETERNITY: + return Instant((float("inf"), float("inf"), float("inf"))) if unit == 'day': if size > 1: @@ -369,7 +370,7 @@ def stop(self) -> types.Instant: month = 1 day -= month_last_day - return periods.Instant((year, month, day)) + return Instant((year, month, day)) @property def last_month(self) -> types.Period: @@ -392,13 +393,13 @@ def last_3_months(self) -> types.Period: """ start: types.Instant = self.first_month.start - return self.__class__((periods.MONTH, start, 3)).offset(-3) + return self.__class__((MONTH, start, 3)).offset(-3) @property def last_year(self) -> types.Period: """Last year of the ``Period``.""" - start: types.Instant = self.start.offset("first-of", periods.YEAR) - return self.__class__((periods.YEAR, start, 1)).offset(-1) + start: types.Instant = self.start.offset("first-of", YEAR) + return self.__class__((YEAR, start, 1)).offset(-1) @property def n_2(self) -> types.Period: @@ -409,8 +410,8 @@ def n_2(self) -> types.Period: """ - start: types.Instant = self.start.offset("first-of", periods.YEAR) - return self.__class__((periods.YEAR, start, 1)).offset(-2) + start: types.Instant = self.start.offset("first-of", YEAR) + return self.__class__((YEAR, start, 1)).offset(-2) @property def this_year(self) -> types.Period: @@ -421,8 +422,8 @@ def this_year(self) -> types.Period: """ - start: types.Instant = self.start.offset("first-of", periods.YEAR) - return self.__class__((periods.YEAR, start, 1)) + start: types.Instant = self.start.offset("first-of", YEAR) + return self.__class__((YEAR, start, 1)) @property def first_month(self) -> types.Period: @@ -433,8 +434,8 @@ def first_month(self) -> types.Period: """ - start: types.Instant = self.start.offset("first-of", periods.MONTH) - return self.__class__((periods.MONTH, start, 1)) + start: types.Instant = self.start.offset("first-of", MONTH) + return self.__class__((MONTH, start, 1)) @property def first_day(self) -> types.Period: @@ -444,7 +445,7 @@ def first_day(self) -> types.Period: A Period. """ - return self.__class__((periods.DAY, self.start, 1)) + return self.__class__((DAY, self.start, 1)) def get_subperiods(self, unit: str) -> Sequence[types.Period]: """Return the list of all the periods of unit ``unit``. @@ -459,27 +460,27 @@ def get_subperiods(self, unit: str) -> Sequence[types.Period]: ValueError: If the period's unit is smaller than the given unit. Examples: - >>> period = periods.Period((periods.YEAR, periods.Instant((2021, 1, 1)), 1)) - >>> period.get_subperiods(periods.MONTH) + >>> period = Period((YEAR, Instant((2021, 1, 1)), 1)) + >>> period.get_subperiods(MONTH) [Period(('month', Instant((2021, 1, 1)), 1)),...2021, 12, 1)), 1))] - >>> period = periods.Period((periods.YEAR, periods.Instant((2021, 1, 1)), 2)) - >>> period.get_subperiods(periods.YEAR) + >>> period = Period((YEAR, Instant((2021, 1, 1)), 2)) + >>> period.get_subperiods(YEAR) [Period(('year', Instant((2021, 1, 1)), 1)),...((2022, 1, 1)), 1))] """ - if periods.unit_weight(self.unit) < periods.unit_weight(unit): + if UNIT_WEIGHTS[self.unit] < UNIT_WEIGHTS[unit]: raise ValueError('Cannot subdivide {0} into {1}'.format(self.unit, unit)) - if unit == periods.YEAR: - return [self.this_year.offset(i, periods.YEAR) for i in range(self.size)] + if unit == YEAR: + return [self.this_year.offset(i, YEAR) for i in range(self.size)] - if unit == periods.MONTH: - return [self.first_month.offset(i, periods.MONTH) for i in range(self.size_in_months)] + if unit == MONTH: + return [self.first_month.offset(i, MONTH) for i in range(self.size_in_months)] - if unit == periods.DAY: - return [self.first_day.offset(i, periods.DAY) for i in range(self.size_in_days)] + if unit == DAY: + return [self.first_day.offset(i, DAY) for i in range(self.size_in_days)] def offset( self, @@ -496,16 +497,16 @@ def offset( Period: A new one. Examples: - >>> periods.Period(("day", periods.Instant((2014, 2, 3)), 1)).offset("first-of", "month") + >>> Period(("day", Instant((2014, 2, 3)), 1)).offset("first-of", "month") Period(('day', Instant((2014, 2, 1)), 1)) - >>> periods.Period(("month", periods.Instant((2014, 2, 3)), 4)).offset("last-of", "month") + >>> Period(("month", Instant((2014, 2, 3)), 4)).offset("last-of", "month") Period(('month', Instant((2014, 2, 28)), 4)) - >>> periods.Period(("day", periods.Instant((2021, 1, 1)), 365)).offset(-3) + >>> Period(("day", Instant((2021, 1, 1)), 365)).offset(-3) Period(('day', Instant((2020, 12, 29)), 365)) - >>> periods.Period(("day", periods.Instant((2021, 1, 1)), 365)).offset(1, "year") + >>> Period(("day", Instant((2021, 1, 1)), 365)).offset(1, "year") Period(('day', Instant((2022, 1, 1)), 365)) """ @@ -522,8 +523,8 @@ def contains(self, other: types.Period) -> bool: True if ``other`` is contained, otherwise False. Example: - >>> period = periods.Period((periods.YEAR, periods.Instant((2021, 1, 1)), 1)) - >>> sub_period = periods.Period((periods.MONTH, periods.Instant((2021, 1, 1)), 3)) + >>> period = Period((YEAR, Instant((2021, 1, 1)), 1)) + >>> sub_period = Period((MONTH, Instant((2021, 1, 1)), 3)) >>> period.contains(sub_period) True diff --git a/openfisca_core/periods/tests/test_helpers.py b/openfisca_core/periods/tests/test_helpers.py index 1951c7eeb6..a6523396a6 100644 --- a/openfisca_core/periods/tests/test_helpers.py +++ b/openfisca_core/periods/tests/test_helpers.py @@ -16,6 +16,7 @@ ["1000-01-01", periods.Instant((1000, 1, 1))], ]) def test_build_instant(arg, expected): + """Returns the expected ``Instant``.""" assert periods.build_instant(arg) == expected @@ -37,6 +38,8 @@ def test_build_instant(arg, expected): ["year:1000-01-01:3", ValueError], ]) def test_build_instant_with_an_invalid_argument(arg, error): + """Raises ``ValueError`` when given an invalid argument.""" + with pytest.raises(error): periods.build_instant(arg) @@ -68,6 +71,7 @@ def test_build_instant_with_an_invalid_argument(arg, error): ["day:1000-01-01:3", periods.Period((periods.DAY, periods.Instant((1000, 1, 1)), 3))], ]) def test_build_period(arg, expected): + """Returns the expected ``Period``.""" assert periods.build_period(arg) == expected @@ -90,6 +94,8 @@ def test_build_period(arg, expected): ["day:1000-01:1", ValueError], ]) def test_build_period_with_an_invalid_argument(arg, error): + """Raises ``ValueError`` when given an invalid argument.""" + with pytest.raises(error): periods.build_period(arg) @@ -106,6 +112,7 @@ def test_build_period_with_an_invalid_argument(arg, error): ["1000-01-99", None], ]) def test_parse_simple_period(arg, expected): + """Returns an ``Instant`` when given a valid ISO format string.""" assert periods.parse_simple_period(arg) == expected @@ -116,4 +123,5 @@ def test_parse_simple_period(arg, expected): [periods.Period((periods.ETERNITY, periods.Instant((1, 1, 1)), 1)), "400_1"], ]) def test_key_period_size_with_a_valid_argument(arg, expected): + """Returns the corresponding period's weight.""" assert periods.key_period_size(arg) == expected diff --git a/openfisca_core/periods/tests/test_instant.py b/openfisca_core/periods/tests/test_instant.py index cc17e5bf8b..50f30efd99 100644 --- a/openfisca_core/periods/tests/test_instant.py +++ b/openfisca_core/periods/tests/test_instant.py @@ -7,6 +7,7 @@ @pytest.fixture def instant(): + """Returns a ``Instant``.""" return periods.Instant((2020, 2, 29)) @@ -14,13 +15,14 @@ def instant(): [None, None], [periods.Instant((1, 1, 1)), datetime.date(1, 1, 1)], [periods.Instant((4, 2, 29)), datetime.date(4, 2, 29)], - [(1, 1, 1), datetime.date(1, 1, 1)], ]) def test_to_date(arg, expected): + """Returns the expected ``date``.""" assert periods.Instant.to_date(arg) == expected @pytest.mark.parametrize("arg, error", [ + [(1, 1, 1), periods.InstantTypeError], [periods.Instant((-1, 1, 1)), ValueError], [periods.Instant((1, -1, 1)), ValueError], [periods.Instant((1, 1, -1)), ValueError], @@ -29,6 +31,8 @@ def test_to_date(arg, expected): [periods.Instant((1, 2, 29)), ValueError], ]) def test_to_date_with_an_invalid_argument(arg, error): + """Raises ``ValueError`` when given an invalid argument.""" + with pytest.raises(error): periods.Instant.to_date(arg) @@ -48,4 +52,5 @@ def test_to_date_with_an_invalid_argument(arg, error): [3, periods.DAY, periods.Instant((2020, 3, 3))], ]) def test_offset(instant, offset, unit, expected): + """Returns the expected ``Instant``.""" assert instant.offset(offset, unit) == expected diff --git a/openfisca_core/periods/tests/test_period.py b/openfisca_core/periods/tests/test_period.py index 723c806ad3..5801740537 100644 --- a/openfisca_core/periods/tests/test_period.py +++ b/openfisca_core/periods/tests/test_period.py @@ -5,6 +5,7 @@ @pytest.fixture def instant(): + """Returns a ``Instant``.""" return periods.Instant((2022, 12, 31)) @@ -17,6 +18,7 @@ def instant(): [periods.YEAR, periods.Instant((2022, 1, 3)), 3, "year:2022:3"], ]) def test_str_with_years(date_unit, instant, size, expected): + """Returns the expected string.""" assert str(periods.Period((date_unit, instant, size))) == expected @@ -26,6 +28,7 @@ def test_str_with_years(date_unit, instant, size, expected): [periods.MONTH, periods.Instant((2022, 3, 1)), 3, "month:2022-03:3"], ]) def test_str_with_months(date_unit, instant, size, expected): + """Returns the expected string.""" assert str(periods.Period((date_unit, instant, size))) == expected @@ -35,6 +38,7 @@ def test_str_with_months(date_unit, instant, size, expected): [periods.DAY, periods.Instant((2022, 3, 1)), 3, "day:2022-03-01:3"], ]) def test_str_with_days(date_unit, instant, size, expected): + """Returns the expected string.""" assert str(periods.Period((date_unit, instant, size))) == expected @@ -47,8 +51,11 @@ def test_str_with_days(date_unit, instant, size, expected): [periods.DAY, periods.DAY, periods.Instant((2022, 12, 31)), periods.Instant((2023, 1, 2)), 3], ]) def test_subperiods(instant, period_unit, unit, start, cease, count): + """Returns the expected subperiods.""" + period = periods.Period((period_unit, instant, 3)) subperiods = period.get_subperiods(unit) + assert len(subperiods) == count assert subperiods[0] == periods.Period((unit, start, 1)) assert subperiods[-1] == periods.Period((unit, cease, 1)) @@ -78,7 +85,10 @@ def test_subperiods(instant, period_unit, unit, start, cease, count): [periods.DAY, 3, periods.DAY, periods.Period(('day', periods.Instant((2023, 1, 3)), 3))], ]) def test_offset(instant, period_unit, offset, unit, expected): + """Returns the expected ``Period``.""" + period = periods.Period((period_unit, instant, 3)) + assert period.offset(offset, unit) == expected @@ -92,7 +102,10 @@ def test_offset(instant, period_unit, offset, unit, expected): [periods.YEAR, periods.Instant((2022, 1, 1)), 2, 24], ]) def test_day_size_in_months(date_unit, instant, size, expected): + """Returns the expected number of months.""" + period = periods.Period((date_unit, instant, size)) + assert period.size_in_months == expected @@ -108,5 +121,8 @@ def test_day_size_in_months(date_unit, instant, size, expected): [periods.YEAR, periods.Instant((2022, 1, 1)), 2, 730], ]) def test_day_size_in_days(date_unit, instant, size, expected): + """Returns the expected number of days.""" + period = periods.Period((date_unit, instant, size)) + assert period.size_in_days == expected diff --git a/openfisca_core/simulations/simulation.py b/openfisca_core/simulations/simulation.py index 17cb73ebf7..2c207c93e2 100644 --- a/openfisca_core/simulations/simulation.py +++ b/openfisca_core/simulations/simulation.py @@ -171,7 +171,7 @@ def calculate_add(self, variable_name: str, period): period = periods.build_period(period) # Check that the requested period matches definition_period - if periods.unit_weight(variable.definition_period) > periods.unit_weight(period.unit): + if periods.UNIT_WEIGHTS[variable.definition_period] > periods.UNIT_WEIGHTS[period.unit]: raise ValueError("Unable to compute variable '{0}' for period {1}: '{0}' can only be computed for {2}-long periods. You can use the DIVIDE option to get an estimate of {0} by dividing the yearly value by 12, or change the requested period to 'period.this_year'.".format( variable.name, period, diff --git a/openfisca_core/types/_domain.py b/openfisca_core/types/_domain.py index 951e752b1c..48452e403e 100644 --- a/openfisca_core/types/_domain.py +++ b/openfisca_core/types/_domain.py @@ -55,6 +55,7 @@ def get_memory_usage(self) -> Any: """Abstract method.""" +@typing_extensions.runtime_checkable class Instant(Protocol): """Instant protocol.""" diff --git a/setup.cfg b/setup.cfg index d66ba84ea3..dfa559dd75 100644 --- a/setup.cfg +++ b/setup.cfg @@ -55,7 +55,7 @@ ignore_errors = True ignore_errors = True [mypy-openfisca_core.periods.tests.*] -ignore_errors = True +ignore_errors = True [mypy-openfisca_core.scripts.*] ignore_errors = True diff --git a/tests/web_api/test_calculate.py b/tests/web_api/test_calculate.py index 0fcc67f0b7..a12abb0363 100644 --- a/tests/web_api/test_calculate.py +++ b/tests/web_api/test_calculate.py @@ -41,8 +41,8 @@ def check_response(client, data, expected_error_code, path_to_check, content_to_ ('{"persons": {"bob": {}}, "households": {"household": {"parents": ["unexpected_person_id"]}}}', client.BAD_REQUEST, 'households/household/parents', 'has not been declared in persons',), ('{"persons": {"bob": {}}, "households": {"household": {"parents": ["bob", "bob"]}}}', client.BAD_REQUEST, 'households/household/parents', 'has been declared more than once',), ('{"persons": {"bob": {}}, "households": {"household": {"parents": ["bob", {}]}}}', client.BAD_REQUEST, 'households/household/parents/1', 'Invalid type',), - ('{"persons": {"bob": {"salary": {"invalid period": 2000 }}}}', client.BAD_REQUEST, 'persons/bob/salary', 'Expected a period',), - ('{"persons": {"bob": {"salary": {"invalid period": null }}}}', client.BAD_REQUEST, 'persons/bob/salary', 'Expected a period',), + ('{"persons": {"bob": {"salary": {"invalid period": 2000 }}}}', client.BAD_REQUEST, 'persons/bob/salary', 'is not a valid period',), + ('{"persons": {"bob": {"salary": {"invalid period": null }}}}', client.BAD_REQUEST, 'persons/bob/salary', 'is not a valid period',), ('{"persons": {"bob": {"basic_income": {"2017": 2000 }}}, "households": {"household": {"parents": ["bob"]}}}', client.BAD_REQUEST, 'persons/bob/basic_income/2017', '"basic_income" can only be set for one month',), ('{"persons": {"bob": {"salary": {"ETERNITY": 2000 }}}, "households": {"household": {"parents": ["bob"]}}}', client.BAD_REQUEST, 'persons/bob/salary/ETERNITY', 'salary is only defined for months',), ('{"persons": {"alice": {}, "bob": {}, "charlie": {}}, "households": {"_": {"parents": ["alice", "bob", "charlie"]}}}', client.BAD_REQUEST, 'households/_/parents', 'at most 2 parents in a household',), @@ -268,7 +268,7 @@ def test_encoding_period_id(test_client): response_json = json.loads(response.data.decode('utf-8')) # In Python 3, there is no encoding issue. - if "Expected a period" not in str(response.data): + if "is not a valid period" not in str(response.data): message = "'à' is not a valid ASCII value." text = response_json['error'] assert message in text From 6eb90a2027e1897be1250279cbf2086afd6c6e42 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Wed, 14 Dec 2022 05:51:24 +0100 Subject: [PATCH 56/70] Make stricter --- CHANGELOG.md | 1 + openfisca_core/periods/__init__.py | 6 +- openfisca_core/periods/_funcs.py | 99 ++++++++++++++----- .../tests/{test_helpers.py => test_funcs.py} | 14 +-- openfisca_core/periods/tests/test_instant.py | 2 +- setup.py | 8 +- 6 files changed, 88 insertions(+), 42 deletions(-) rename openfisca_core/periods/tests/{test_helpers.py => test_funcs.py} (91%) diff --git a/CHANGELOG.md b/CHANGELOG.md index afd5ba81f5..ca109d572d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - Rename `instant` to `build_instant`. - Rename `period` to `build_period`. - Move `instant_date` to `Instant.to_date`. +- Make `periods.parse_period` stricter (for example `2022-1` now fails). #### Technical changes diff --git a/openfisca_core/periods/__init__.py b/openfisca_core/periods/__init__.py index 3ba24cf9db..d2efda1d34 100644 --- a/openfisca_core/periods/__init__.py +++ b/openfisca_core/periods/__init__.py @@ -31,15 +31,11 @@ INSTANT_PATTERN, ) -from ._errors import ( # noqa: F401 - InstantTypeError, - ) - from ._funcs import ( # noqa: F401 build_instant, build_period, key_period_size, - parse_simple_period, + parse_period, ) from ._units import ( # noqa: F401 diff --git a/openfisca_core/periods/_funcs.py b/openfisca_core/periods/_funcs.py index aec62e0519..4f27be831b 100644 --- a/openfisca_core/periods/_funcs.py +++ b/openfisca_core/periods/_funcs.py @@ -4,6 +4,10 @@ import datetime +import pendulum +from pendulum.datetime import Date +from pendulum.parsing import ParserError + from openfisca_core import types from ._config import INSTANT_PATTERN @@ -120,16 +124,16 @@ def build_period(value: Any) -> types.Period: >>> build_period("year:2014") Period(('year', Instant((2014, 1, 1)), 1)) - >>> build_period("month:2014-2") + >>> build_period("month:2014-02") Period(('month', Instant((2014, 2, 1)), 1)) - >>> build_period("year:2014-2") + >>> build_period("year:2014-02") Period(('year', Instant((2014, 2, 1)), 1)) - >>> build_period("day:2014-2-2") + >>> build_period("day:2014-02-02") Period(('day', Instant((2014, 2, 2)), 1)) - >>> build_period("day:2014-2-2:3") + >>> build_period("day:2014-02-02:3") Period(('day', Instant((2014, 2, 2)), 3)) """ @@ -150,7 +154,7 @@ def build_period(value: Any) -> types.Period: raise PeriodFormatError(value) # Try to parse as a simple period - period = parse_simple_period(value) + period = parse_period(value) if period is not None: return period @@ -168,7 +172,7 @@ def build_period(value: Any) -> types.Period: raise PeriodFormatError(value) # Middle component must be a valid iso period - base_period = parse_simple_period(components[1]) + base_period = parse_period(components[1]) if not base_period: raise PeriodFormatError(value) @@ -225,8 +229,8 @@ def key_period_size(period: types.Period) -> str: return f"{UNIT_WEIGHTS[unit]}_{size}" -def parse_simple_period(value: str) -> Optional[types.Period]: - """Parse simple periods respecting the ISO format. +def parse_period(value: str) -> Optional[types.Period]: + """Parse periods respecting the ISO format. Args: value: A string such as such as "2012" or "2015-03". @@ -234,37 +238,80 @@ def parse_simple_period(value: str) -> Optional[types.Period]: Returns: A Period. + Raises: + AttributeError: When arguments are invalid, like ``"-1"``. + ValueError: When values are invalid, like ``"2022-32-13"``. + Examples: - >>> parse_simple_period("2022") + >>> parse_period("2022") Period(('year', Instant((2022, 1, 1)), 1)) - >>> parse_simple_period("2022-02") + >>> parse_period("2022-02") Period(('month', Instant((2022, 2, 1)), 1)) - >>> parse_simple_period("2022-02-13") + >>> parse_period("2022-02-13") Period(('day', Instant((2022, 2, 13)), 1)) """ + # If it's a complex period, next! + if len(value.split(":")) != 1: + return None + + # Check for a non-empty string. + if not (value and isinstance(value, str)): + raise AttributeError + + # If it's negative, next! + if value[0] == "-": + raise ValueError + + unit = _parse_unit(value) + try: - date = datetime.datetime.strptime(value, '%Y') + date = pendulum.parse(value, exact = True) - except ValueError: - try: - date = datetime.datetime.strptime(value, '%Y-%m') + except ParserError: + return None - except ValueError: - try: - date = datetime.datetime.strptime(value, '%Y-%m-%d') + if not isinstance(date, Date): + raise ValueError - except ValueError: - return None + instant = Instant((date.year, date.month, date.day)) - else: - return Period((DAY, Instant((date.year, date.month, date.day)), 1)) + return Period((unit, instant, 1)) - else: - return Period((MONTH, Instant((date.year, date.month, 1)), 1)) - else: - return Period((YEAR, Instant((date.year, date.month, 1)), 1)) +def _parse_unit(value: str) -> str: + """Determine the date unit of a date string. + + Args: + value (str): The date string to parse. + + Returns: + A date unit. + + Raises: + ValueError: when no date unit can be determined. + + Examples: + >>> _parse_unit("2022") + 'year' + + >>> _parse_unit("2022-03-01") + 'day' + + """ + + length = len(value.split("-")) + + if length == 1: + return YEAR + + elif length == 2: + return MONTH + + elif length == 3: + return DAY + + raise ValueError diff --git a/openfisca_core/periods/tests/test_helpers.py b/openfisca_core/periods/tests/test_funcs.py similarity index 91% rename from openfisca_core/periods/tests/test_helpers.py rename to openfisca_core/periods/tests/test_funcs.py index a6523396a6..b2fbd59cd8 100644 --- a/openfisca_core/periods/tests/test_helpers.py +++ b/openfisca_core/periods/tests/test_funcs.py @@ -52,8 +52,6 @@ def test_build_instant_with_an_invalid_argument(arg, error): [periods.Period((periods.DAY, periods.Instant((1, 1, 1)), 365)), periods.Period((periods.DAY, periods.Instant((1, 1, 1)), 365))], [1000, periods.Period((periods.YEAR, periods.Instant((1000, 1, 1)), 1))], ["1000", periods.Period((periods.YEAR, periods.Instant((1000, 1, 1)), 1))], - ["1000-1", periods.Period((periods.MONTH, periods.Instant((1000, 1, 1)), 1))], - ["1000-1-1", periods.Period((periods.DAY, periods.Instant((1000, 1, 1)), 1))], ["1000-01", periods.Period((periods.MONTH, periods.Instant((1000, 1, 1)), 1))], ["1000-01-01", periods.Period((periods.DAY, periods.Instant((1000, 1, 1)), 1))], ["1004-02-29", periods.Period((periods.DAY, periods.Instant((1004, 2, 29)), 1))], @@ -81,10 +79,12 @@ def test_build_period(arg, expected): [datetime.date(1, 1, 1), ValueError], ["1000:1", ValueError], ["1000-0", ValueError], + ["1000-1", ValueError], ["1000-13", ValueError], ["1000-01:1", ValueError], ["1000-0-0", ValueError], ["1000-1-0", ValueError], + ["1000-1-1", ValueError], ["1000-2-31", ValueError], ["1000-01-01:1", ValueError], ["month:1000", ValueError], @@ -104,16 +104,16 @@ def test_build_period_with_an_invalid_argument(arg, error): ["1", None], ["999", None], ["1000", periods.Period((periods.YEAR, periods.Instant((1000, 1, 1)), 1))], - ["1000-1", periods.Period((periods.MONTH, periods.Instant((1000, 1, 1)), 1))], ["1000-01", periods.Period((periods.MONTH, periods.Instant((1000, 1, 1)), 1))], - ["1000-1-1", periods.Period((periods.DAY, periods.Instant((1000, 1, 1)), 1))], - ["1000-01-1", periods.Period((periods.DAY, periods.Instant((1000, 1, 1)), 1))], ["1000-01-01", periods.Period((periods.DAY, periods.Instant((1000, 1, 1)), 1))], + ["1000-1", None], + ["1000-1-1", None], + ["1000-01-1", None], ["1000-01-99", None], ]) -def test_parse_simple_period(arg, expected): +def test_parse_period(arg, expected): """Returns an ``Instant`` when given a valid ISO format string.""" - assert periods.parse_simple_period(arg) == expected + assert periods.parse_period(arg) == expected @pytest.mark.parametrize("arg, expected", [ diff --git a/openfisca_core/periods/tests/test_instant.py b/openfisca_core/periods/tests/test_instant.py index 50f30efd99..63265e2766 100644 --- a/openfisca_core/periods/tests/test_instant.py +++ b/openfisca_core/periods/tests/test_instant.py @@ -22,7 +22,7 @@ def test_to_date(arg, expected): @pytest.mark.parametrize("arg, error", [ - [(1, 1, 1), periods.InstantTypeError], + [(1, 1, 1), TypeError], [periods.Instant((-1, 1, 1)), ValueError], [periods.Instant((1, -1, 1)), ValueError], [periods.Instant((1, 1, -1)), ValueError], diff --git a/setup.py b/setup.py index 2355296ea0..7be9792e5f 100644 --- a/setup.py +++ b/setup.py @@ -26,13 +26,15 @@ # functional and integration breaks caused by external code updates. general_requirements = [ + 'PyYAML >= 3.10', 'dpath >= 1.5.0, < 3.0.0', + 'importlib-metadata < 4.3.0', 'nptyping == 1.4.4', 'numexpr >= 2.7.0, <= 3.0', - 'numpy >= 1.11, < 1.21', + 'numpy >= 1.20, < 1.21', + 'pendulum >= 2.1.0, < 3.0.0', 'psutil >= 5.4.7, < 6.0.0', 'pytest >= 4.4.1, < 6.0.0', # For openfisca test - 'PyYAML >= 3.10', 'sortedcontainers == 2.2.2', 'typing-extensions >= 4.0.0, < 5.0.0', ] @@ -56,7 +58,7 @@ 'flake8-rst-docstrings == 0.2.3', 'mypy == 0.910', 'openapi-spec-validator >= 0.3.0', - 'pycodestyle >= 2.7.0, < 2.8.0', + 'pycodestyle >= 2.8.0, < 2.9.0', 'pylint == 2.10.2', 'xdoctest >= 1.0.0, < 2.0.0', ] + api_requirements From 31c2d321b9a1d6bd72f2a57c269f2a1bee213bc5 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Wed, 14 Dec 2022 06:09:08 +0100 Subject: [PATCH 57/70] Refactor _parse_unit --- openfisca_core/periods/_funcs.py | 49 +++++--------------------------- 1 file changed, 7 insertions(+), 42 deletions(-) diff --git a/openfisca_core/periods/_funcs.py b/openfisca_core/periods/_funcs.py index 4f27be831b..3ecb6f2f3d 100644 --- a/openfisca_core/periods/_funcs.py +++ b/openfisca_core/periods/_funcs.py @@ -16,6 +16,8 @@ from .instant_ import Instant from .period_ import Period +UNIT_MAPPING = {1: "year", 2: "month", 3: "day"} + def build_instant(value: Any) -> Optional[types.Instant]: """Build a new instant, aka a triple of integers (year, month, day). @@ -262,12 +264,10 @@ def parse_period(value: str) -> Optional[types.Period]: if not (value and isinstance(value, str)): raise AttributeError - # If it's negative, next! - if value[0] == "-": + # If it's negative period, next! + if value[0] == "-" or len(value.split(":")) != 1: raise ValueError - unit = _parse_unit(value) - try: date = pendulum.parse(value, exact = True) @@ -277,41 +277,6 @@ def parse_period(value: str) -> Optional[types.Period]: if not isinstance(date, Date): raise ValueError - instant = Instant((date.year, date.month, date.day)) - - return Period((unit, instant, 1)) - - -def _parse_unit(value: str) -> str: - """Determine the date unit of a date string. - - Args: - value (str): The date string to parse. - - Returns: - A date unit. - - Raises: - ValueError: when no date unit can be determined. - - Examples: - >>> _parse_unit("2022") - 'year' - - >>> _parse_unit("2022-03-01") - 'day' - - """ - - length = len(value.split("-")) - - if length == 1: - return YEAR - - elif length == 2: - return MONTH - - elif length == 3: - return DAY - - raise ValueError + unit = UNIT_MAPPING[len(value.split("-"))] + start = Instant((date.year, date.month, date.day)) + return Period((unit, start, 1)) From 9a96aed4659ca34f9cff5b441d8fa89fd70b132a Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Wed, 14 Dec 2022 07:31:00 +0100 Subject: [PATCH 58/70] Refactor as --- CHANGELOG.md | 1 + .../data_storage/in_memory_storage.py | 2 +- .../data_storage/on_disk_storage.py | 2 +- openfisca_core/periods/period_.py | 43 ++++++++++--------- openfisca_core/variables/helpers.py | 2 +- 5 files changed, 27 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca109d572d..09b367ff5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - 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 diff --git a/openfisca_core/data_storage/in_memory_storage.py b/openfisca_core/data_storage/in_memory_storage.py index 765a6f836c..d5dc9e2f63 100644 --- a/openfisca_core/data_storage/in_memory_storage.py +++ b/openfisca_core/data_storage/in_memory_storage.py @@ -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): diff --git a/openfisca_core/data_storage/on_disk_storage.py b/openfisca_core/data_storage/on_disk_storage.py index 402b576e6a..f3ba18ebb3 100644 --- a/openfisca_core/data_storage/on_disk_storage.py +++ b/openfisca_core/data_storage/on_disk_storage.py @@ -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): diff --git a/openfisca_core/periods/period_.py b/openfisca_core/periods/period_.py index 060fbfd201..22fa82412f 100644 --- a/openfisca_core/periods/period_.py +++ b/openfisca_core/periods/period_.py @@ -144,6 +144,29 @@ def __str__(self) -> str: # complex period return '{}:{}-{:02d}:{}'.format(unit, year, month, size) + def __contains__(self, other: object) -> bool: + """Checks if a ``period`` contains another one. + + Args: + other (object): The other ``Period``. + + Returns: + True if ``other`` is contained, otherwise False. + + Example: + >>> period = Period((YEAR, Instant((2021, 1, 1)), 1)) + >>> sub_period = Period((MONTH, Instant((2021, 1, 1)), 3)) + + >>> sub_period in period + True + + """ + + if isinstance(other, types.Period): + return self.start <= other.start and self.stop >= other.stop + + return super().__contains__(other) + @property def date(self) -> datetime.date: """The date representation of the ``period``'s' start date. @@ -512,23 +535,3 @@ def offset( """ return self.__class__((self[0], self[1].offset(offset, self[0] if unit is None else unit), self[2])) - - def contains(self, other: types.Period) -> bool: - """Checks if a ``period`` contains another one. - - Args: - other (:obj:`.Period`): The other ``Period``. - - Returns: - True if ``other`` is contained, otherwise False. - - Example: - >>> period = Period((YEAR, Instant((2021, 1, 1)), 1)) - >>> sub_period = Period((MONTH, Instant((2021, 1, 1)), 3)) - - >>> period.contains(sub_period) - True - - """ - - return self.start <= other.start and self.stop >= other.stop diff --git a/openfisca_core/variables/helpers.py b/openfisca_core/variables/helpers.py index 335a585498..7ae026bb99 100644 --- a/openfisca_core/variables/helpers.py +++ b/openfisca_core/variables/helpers.py @@ -17,7 +17,7 @@ def get_annualized_variable(variable: variables.Variable, annualization_period: def make_annual_formula(original_formula, annualization_period = None): def annual_formula(population, period, parameters): - if period.start.month != 1 and (annualization_period is None or annualization_period.contains(period)): + if period.start.month != 1 and (annualization_period is None or period not in annualization_period): return population(variable.name, period.this_year.first_month) if original_formula.__code__.co_argcount == 2: return original_formula(population, period) From 1b79d3cd57506cf4a7b2c22c29814c10ad870740 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Wed, 14 Dec 2022 10:17:05 +0100 Subject: [PATCH 59/70] Fix typing --- openfisca_core/periods/_config.py | 2 +- openfisca_core/periods/_funcs.py | 23 +++--- openfisca_core/periods/instant_.py | 10 +-- openfisca_core/periods/period_.py | 82 ++++++++++--------- .../taxbenefitsystems/tax_benefit_system.py | 13 ++- openfisca_core/types/_domain.py | 11 ++- openfisca_core/variables/variable.py | 9 +- 7 files changed, 79 insertions(+), 71 deletions(-) diff --git a/openfisca_core/periods/_config.py b/openfisca_core/periods/_config.py index 485f5fd44a..f3d3cf2b2a 100644 --- a/openfisca_core/periods/_config.py +++ b/openfisca_core/periods/_config.py @@ -6,4 +6,4 @@ # Matches "2015", "2015-01", "2015-01-01" # Does not match "2015-13", "2015-12-32" -INSTANT_PATTERN: Pattern = re.compile(r"^\d{4}(-(0[1-9]|1[012]))?(-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01]))?$") +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]))?$") diff --git a/openfisca_core/periods/_funcs.py b/openfisca_core/periods/_funcs.py index 3ecb6f2f3d..87c87d376d 100644 --- a/openfisca_core/periods/_funcs.py +++ b/openfisca_core/periods/_funcs.py @@ -59,28 +59,25 @@ def build_instant(value: Any) -> Optional[types.Instant]: if isinstance(value, Instant): return value - if isinstance(value, str): - if not INSTANT_PATTERN.match(value): - raise InstantFormatError(value) + if isinstance(value, Period): + return value.start - instant = Instant( - int(fragment) - for fragment in value.split('-', 2)[:3] - ) + if isinstance(value, str) and not INSTANT_PATTERN.match(value): + raise InstantFormatError(value) + + if isinstance(value, str): + instant = tuple(int(fragment) for fragment in value.split('-', 2)[:3]) elif isinstance(value, datetime.date): - instant = Instant((value.year, value.month, value.day)) + instant = value.year, value.month, value.day elif isinstance(value, int): - instant = (value,) + instant = value, elif isinstance(value, list): assert 1 <= len(value) <= 3 instant = tuple(value) - elif isinstance(value, Period): - instant = value.start - else: assert isinstance(value, tuple), value assert 1 <= len(value) <= 3 @@ -226,7 +223,7 @@ def key_period_size(period: types.Period) -> str: """ - unit, start, size = period + unit, _start, size = period return f"{UNIT_WEIGHTS[unit]}_{size}" diff --git a/openfisca_core/periods/instant_.py b/openfisca_core/periods/instant_.py index 0e8eb8dd2f..0d4c7e5fc1 100644 --- a/openfisca_core/periods/instant_.py +++ b/openfisca_core/periods/instant_.py @@ -5,13 +5,11 @@ import calendar import datetime -from openfisca_core import types - from ._errors import DateUnitValueError, InstantTypeError, OffsetTypeError from ._units import DAY, MONTH, YEAR -class Instant(tuple): +class Instant(tuple[int, int, int]): """An instant in time (``year``, ``month``, ``day``). An ``Instant`` represents the most atomic and indivisible @@ -85,7 +83,7 @@ def __str__(self) -> str: return str(self) @staticmethod - def to_date(value: Optional[types.Instant]) -> Optional[datetime.date]: + def to_date(value: Optional[Instant]) -> Optional[datetime.date]: """Returns the date representation of an ``Instant``. Args: @@ -108,7 +106,7 @@ def to_date(value: Optional[types.Instant]) -> Optional[datetime.date]: if value is None: return None - if isinstance(value, types.Instant): + if isinstance(value, Instant): return value.date raise InstantTypeError(value) @@ -184,7 +182,7 @@ def date(self) -> datetime.date: return self.date - def offset(self, offset: Union[str, int], unit: str) -> types.Instant: + def offset(self, offset: Union[str, int], unit: str) -> Instant: """Increments/decrements the given instant with offset units. Args: diff --git a/openfisca_core/periods/period_.py b/openfisca_core/periods/period_.py index 22fa82412f..60eb690d32 100644 --- a/openfisca_core/periods/period_.py +++ b/openfisca_core/periods/period_.py @@ -5,13 +5,12 @@ import calendar import datetime -from openfisca_core import types - +from ._errors import DateUnitValueError from ._units import DAY, MONTH, YEAR, ETERNITY, UNIT_WEIGHTS from .instant_ import Instant -class Period(tuple): +class Period(tuple[str, Instant, int]): """Toolbox to handle date intervals. A ``Period`` is a triple (``unit``, ``start``, ``size``). @@ -121,28 +120,28 @@ def __str__(self) -> str: year, month, day = start_instant # 1 year long period - if (unit == MONTH and size == 12 or unit == YEAR and size == 1): + if unit == MONTH and size == 12 or unit == YEAR and size == 1: if month == 1: # civil year starting from january return str(year) else: # rolling year - return '{}:{}-{:02d}'.format(YEAR, year, month) + return f'{YEAR}:{year}-{month:02d}' # simple month if unit == MONTH and size == 1: - return '{}-{:02d}'.format(year, month) + return f'{year}-{month:02d}' # several civil years if unit == YEAR and month == 1: - return '{}:{}:{}'.format(unit, year, size) + return f'{unit}:{year}:{size}' if unit == DAY: if size == 1: - return '{}-{:02d}-{:02d}'.format(year, month, day) + return f'{year}-{month:02d}-{day:02d}' else: - return '{}:{}-{:02d}-{:02d}:{}'.format(unit, year, month, day, size) + return f'{unit}:{year}-{month:02d}-{day:02d}:{size}' # complex period - return '{}:{}-{:02d}:{}'.format(unit, year, month, size) + return f'{unit}:{year}-{month:02d}:{size}' def __contains__(self, other: object) -> bool: """Checks if a ``period`` contains another one. @@ -162,7 +161,7 @@ def __contains__(self, other: object) -> bool: """ - if isinstance(other, types.Period): + if isinstance(other, Period): return self.start <= other.start and self.stop >= other.stop return super().__contains__(other) @@ -273,10 +272,10 @@ def size_in_months(self) -> int: """ - if (self[0] == MONTH): + if self[0] == MONTH: return self[2] - if(self[0] == YEAR): + if self[0] == YEAR: return self[2] * 12 raise ValueError(f"Cannot calculate number of months in {self[0]}.") @@ -315,7 +314,7 @@ def size_in_days(self) -> int: raise ValueError(f"Cannot calculate number of days in {unit}") @property - def start(self) -> types.Instant: + def start(self) -> Instant: """The ``Instant`` at which the ``Period`` starts. Returns: @@ -332,7 +331,7 @@ def start(self) -> types.Instant: return self[1] @property - def stop(self) -> types.Instant: + def stop(self) -> Instant: """Last day of the ``Period`` as an ``Instant``. Returns: @@ -354,7 +353,7 @@ def stop(self) -> types.Instant: year, month, day = start_instant if unit == ETERNITY: - return Instant((float("inf"), float("inf"), float("inf"))) + return Instant((1, 1, 1)) if unit == 'day': if size > 1: @@ -375,7 +374,7 @@ def stop(self) -> types.Instant: year += 1 month -= 12 else: - assert unit == 'year', 'Invalid unit: {} of type {}'.format(unit, type(unit)) + assert unit == 'year', f'Invalid unit: {unit} of type {type(unit)}' year += size day -= 1 if day < 1: @@ -396,7 +395,7 @@ def stop(self) -> types.Instant: return Instant((year, month, day)) @property - def last_month(self) -> types.Period: + def last_month(self) -> Period: """Last month of the ``Period``. Returns: @@ -407,7 +406,7 @@ def last_month(self) -> types.Period: return self.first_month.offset(-1) @property - def last_3_months(self) -> types.Period: + def last_3_months(self) -> Period: """Last 3 months of the ``Period``. Returns: @@ -415,17 +414,17 @@ def last_3_months(self) -> types.Period: """ - start: types.Instant = self.first_month.start - return self.__class__((MONTH, start, 3)).offset(-3) + start: Instant = self.first_month.start + return Period((MONTH, start, 3)).offset(-3) @property - def last_year(self) -> types.Period: + def last_year(self) -> Period: """Last year of the ``Period``.""" - start: types.Instant = self.start.offset("first-of", YEAR) - return self.__class__((YEAR, start, 1)).offset(-1) + start: Instant = self.start.offset("first-of", YEAR) + return Period((YEAR, start, 1)).offset(-1) @property - def n_2(self) -> types.Period: + def n_2(self) -> Period: """Last 2 years of the ``Period``. Returns: @@ -433,11 +432,11 @@ def n_2(self) -> types.Period: """ - start: types.Instant = self.start.offset("first-of", YEAR) - return self.__class__((YEAR, start, 1)).offset(-2) + start: Instant = self.start.offset("first-of", YEAR) + return Period((YEAR, start, 1)).offset(-2) @property - def this_year(self) -> types.Period: + def this_year(self) -> Period: """A new year ``Period`` starting at the beginning of the year. Returns: @@ -445,11 +444,11 @@ def this_year(self) -> types.Period: """ - start: types.Instant = self.start.offset("first-of", YEAR) - return self.__class__((YEAR, start, 1)) + start: Instant = self.start.offset("first-of", YEAR) + return Period((YEAR, start, 1)) @property - def first_month(self) -> types.Period: + def first_month(self) -> Period: """A new month ``Period`` starting at the first of the month. Returns: @@ -457,29 +456,30 @@ def first_month(self) -> types.Period: """ - start: types.Instant = self.start.offset("first-of", MONTH) - return self.__class__((MONTH, start, 1)) + start: Instant = self.start.offset("first-of", MONTH) + return Period((MONTH, start, 1)) @property - def first_day(self) -> types.Period: + def first_day(self) -> Period: """A new day ``Period``. Returns: A Period. """ - return self.__class__((DAY, self.start, 1)) + return Period((DAY, self.start, 1)) - def get_subperiods(self, unit: str) -> Sequence[types.Period]: + def get_subperiods(self, unit: str) -> Sequence[Period]: """Return the list of all the periods of unit ``unit``. Args: unit: A string representing period's ``unit``. Returns: - A list of Periods. + A list of periods. Raises: + DateUnitValueError: If the ``unit`` is not a valid date unit. ValueError: If the period's unit is smaller than the given unit. Examples: @@ -494,7 +494,7 @@ def get_subperiods(self, unit: str) -> Sequence[types.Period]: """ if UNIT_WEIGHTS[self.unit] < UNIT_WEIGHTS[unit]: - raise ValueError('Cannot subdivide {0} into {1}'.format(self.unit, unit)) + raise ValueError(f"Cannot subdivide {self.unit} into {unit}") if unit == YEAR: return [self.this_year.offset(i, YEAR) for i in range(self.size)] @@ -505,11 +505,13 @@ def get_subperiods(self, unit: str) -> Sequence[types.Period]: if unit == DAY: return [self.first_day.offset(i, DAY) for i in range(self.size_in_days)] + raise DateUnitValueError(unit) + def offset( self, offset: Union[str, int], unit: Optional[str] = None, - ) -> types.Period: + ) -> Period: """Increment (or decrement) the given period with offset units. Args: @@ -534,4 +536,4 @@ def offset( """ - return self.__class__((self[0], self[1].offset(offset, self[0] if unit is None else unit), self[2])) + return Period((self[0], self[1].offset(offset, self[0] if unit is None else unit), self[2])) diff --git a/openfisca_core/taxbenefitsystems/tax_benefit_system.py b/openfisca_core/taxbenefitsystems/tax_benefit_system.py index 4af239ca52..cfb1245b0f 100644 --- a/openfisca_core/taxbenefitsystems/tax_benefit_system.py +++ b/openfisca_core/taxbenefitsystems/tax_benefit_system.py @@ -14,14 +14,13 @@ import traceback import typing -from openfisca_core import commons, periods, variables +from openfisca_core import commons, periods, types, variables from openfisca_core.entities import Entity from openfisca_core.errors import VariableNameConflictError, VariableNotFoundError from openfisca_core.parameters import ParameterNode from openfisca_core.periods import Instant, Period from openfisca_core.populations import Population, GroupPopulation from openfisca_core.simulations import SimulationBuilder -from openfisca_core.types import ParameterNodeAtInstant from openfisca_core.variables import Variable log = logging.getLogger(__name__) @@ -43,7 +42,7 @@ class TaxBenefitSystem: person_entity: Entity _base_tax_benefit_system = None - _parameters_at_instant_cache: Dict[Instant, ParameterNodeAtInstant] = {} + _parameters_at_instant_cache: Dict[Instant, types.ParameterNodeAtInstant] = {} person_key_plural = None preprocess_parameters = None baseline = None # Baseline tax-benefit system. Used only by reforms. Note: Reforms can be chained. @@ -385,8 +384,8 @@ def _get_baseline_parameters_at_instant(self, instant): @functools.lru_cache() def get_parameters_at_instant( self, - instant: Union[str, int, Period, Instant], - ) -> Optional[ParameterNodeAtInstant]: + instant: Union[str, int, types.Period, types.Instant], + ) -> Optional[types.ParameterNodeAtInstant]: """Get the parameters of the legislation at a given instant Args: @@ -397,7 +396,7 @@ def get_parameters_at_instant( """ - key: Instant + key: Optional[types.Instant] msg: str if isinstance(instant, Instant): @@ -410,7 +409,7 @@ def get_parameters_at_instant( key = periods.build_instant(instant) else: - msg = f"Expected an Instant (e.g. Instant((2017, 1, 1)) ). Got: {key}." + msg = f"Expected an Instant (e.g. Instant((2017, 1, 1)) ). Got: {instant}." raise AssertionError(msg) if self.parameters is None: diff --git a/openfisca_core/types/_domain.py b/openfisca_core/types/_domain.py index 48452e403e..7eed350a78 100644 --- a/openfisca_core/types/_domain.py +++ b/openfisca_core/types/_domain.py @@ -86,9 +86,8 @@ def __call__(self, instant: Instant) -> ParameterNodeAtInstant: class Period(Protocol): """Period protocol.""" - @property @abc.abstractmethod - def start(self) -> Any: + def __iter__(self) -> Any: """Abstract method.""" @property @@ -96,14 +95,20 @@ def start(self) -> Any: def unit(self) -> Any: """Abstract method.""" + @property @abc.abstractmethod - def offset(self, offset: Any, unit: Any = None) -> Any: + def start(self) -> Any: """Abstract method.""" + @property @abc.abstractmethod def stop(self) -> Any: """Abstract method.""" + @abc.abstractmethod + def offset(self, offset: Any, unit: Any = None) -> Any: + """Abstract method.""" + class Population(Protocol): """Population protocol.""" diff --git a/openfisca_core/variables/variable.py b/openfisca_core/variables/variable.py index eeb9c33169..be2ea03aad 100644 --- a/openfisca_core/variables/variable.py +++ b/openfisca_core/variables/variable.py @@ -326,6 +326,8 @@ def get_formula( """ + instant: Optional[Instant] + if not self.formulas: return None @@ -334,11 +336,16 @@ def get_formula( if isinstance(period, Period): instant = period.start + else: try: instant = periods.build_period(period).start + except ValueError: - instant = periods.instant(period) + instant = periods.build_instant(period) + + if instant is None: + return None if self.end and instant.date > self.end: return None From 15a86783d57d19bbac8c3f7829dc427a2bfbb9d7 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Wed, 14 Dec 2022 10:50:20 +0100 Subject: [PATCH 60/70] Fix typing in py3.7 --- openfisca_core/periods/instant_.py | 4 ++-- openfisca_core/periods/period_.py | 4 ++-- openfisca_core/taxbenefitsystems/tax_benefit_system.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/openfisca_core/periods/instant_.py b/openfisca_core/periods/instant_.py index 0d4c7e5fc1..1aacd46dcb 100644 --- a/openfisca_core/periods/instant_.py +++ b/openfisca_core/periods/instant_.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Dict, Optional, Union +from typing import Dict, Optional, Tuple, Union import calendar import datetime @@ -9,7 +9,7 @@ from ._units import DAY, MONTH, YEAR -class Instant(tuple[int, int, int]): +class Instant(Tuple[int, int, int]): """An instant in time (``year``, ``month``, ``day``). An ``Instant`` represents the most atomic and indivisible diff --git a/openfisca_core/periods/period_.py b/openfisca_core/periods/period_.py index 60eb690d32..ea7f1fc341 100644 --- a/openfisca_core/periods/period_.py +++ b/openfisca_core/periods/period_.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Optional, Sequence, Union +from typing import Optional, Sequence, Tuple, Union import calendar import datetime @@ -10,7 +10,7 @@ from .instant_ import Instant -class Period(tuple[str, Instant, int]): +class Period(Tuple[str, Instant, int]): """Toolbox to handle date intervals. A ``Period`` is a triple (``unit``, ``start``, ``size``). diff --git a/openfisca_core/taxbenefitsystems/tax_benefit_system.py b/openfisca_core/taxbenefitsystems/tax_benefit_system.py index cfb1245b0f..35a90262d9 100644 --- a/openfisca_core/taxbenefitsystems/tax_benefit_system.py +++ b/openfisca_core/taxbenefitsystems/tax_benefit_system.py @@ -399,10 +399,10 @@ def get_parameters_at_instant( key: Optional[types.Instant] msg: str - if isinstance(instant, Instant): + if isinstance(instant, types.Instant): key = instant - elif isinstance(instant, Period): + elif isinstance(instant, types.Period): key = instant.start elif isinstance(instant, (str, int)): From c56c2a24d5f37249e054b65367ee76532eea6daa Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Wed, 14 Dec 2022 11:00:04 +0100 Subject: [PATCH 61/70] Rename period & instant --- openfisca_core/periods/__init__.py | 4 ++-- openfisca_core/periods/_funcs.py | 4 ++-- openfisca_core/periods/{instant_.py => instant.py} | 0 openfisca_core/periods/{period_.py => period.py} | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) rename openfisca_core/periods/{instant_.py => instant.py} (100%) rename openfisca_core/periods/{period_.py => period.py} (99%) diff --git a/openfisca_core/periods/__init__.py b/openfisca_core/periods/__init__.py index d2efda1d34..8f0f18fbcc 100644 --- a/openfisca_core/periods/__init__.py +++ b/openfisca_core/periods/__init__.py @@ -46,5 +46,5 @@ UNIT_WEIGHTS, ) -from .instant_ import Instant # noqa: F401 -from .period_ import Period # noqa: F401 +from .instant import Instant # noqa: F401 +from .period import Period # noqa: F401 diff --git a/openfisca_core/periods/_funcs.py b/openfisca_core/periods/_funcs.py index 87c87d376d..2431045dca 100644 --- a/openfisca_core/periods/_funcs.py +++ b/openfisca_core/periods/_funcs.py @@ -13,8 +13,8 @@ from ._config import INSTANT_PATTERN from ._errors import InstantFormatError, PeriodFormatError from ._units import DAY, ETERNITY, MONTH, YEAR, UNIT_WEIGHTS -from .instant_ import Instant -from .period_ import Period +from .instant import Instant +from .period import Period UNIT_MAPPING = {1: "year", 2: "month", 3: "day"} diff --git a/openfisca_core/periods/instant_.py b/openfisca_core/periods/instant.py similarity index 100% rename from openfisca_core/periods/instant_.py rename to openfisca_core/periods/instant.py diff --git a/openfisca_core/periods/period_.py b/openfisca_core/periods/period.py similarity index 99% rename from openfisca_core/periods/period_.py rename to openfisca_core/periods/period.py index ea7f1fc341..5edfde84ee 100644 --- a/openfisca_core/periods/period_.py +++ b/openfisca_core/periods/period.py @@ -7,7 +7,7 @@ from ._errors import DateUnitValueError from ._units import DAY, MONTH, YEAR, ETERNITY, UNIT_WEIGHTS -from .instant_ import Instant +from .instant import Instant class Period(Tuple[str, Instant, int]): From 51e23123b67ae025d406a1f8f047e6d49b94fddb Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Wed, 14 Dec 2022 11:22:32 +0100 Subject: [PATCH 62/70] Fix imports --- openfisca_core/periods/__init__.py | 22 +++------------------- openfisca_core/periods/_errors.py | 1 - openfisca_core/periods/_funcs.py | 2 +- openfisca_core/periods/period.py | 2 +- setup.cfg | 3 --- 5 files changed, 5 insertions(+), 25 deletions(-) diff --git a/openfisca_core/periods/__init__.py b/openfisca_core/periods/__init__.py index 8f0f18fbcc..5185fc9110 100644 --- a/openfisca_core/periods/__init__.py +++ b/openfisca_core/periods/__init__.py @@ -27,24 +27,8 @@ """ -from ._config import ( # noqa: F401 - INSTANT_PATTERN, - ) - -from ._funcs import ( # noqa: F401 - build_instant, - build_period, - key_period_size, - parse_period, - ) - -from ._units import ( # noqa: F401 - DAY, - ETERNITY, - MONTH, - YEAR, - UNIT_WEIGHTS, - ) - +from ._config import INSTANT_PATTERN # noqa: F401 +from ._funcs import build_instant, build_period, key_period_size, parse_period # noqa: F401 +from ._units import DAY, ETERNITY, MONTH, UNIT_WEIGHTS, YEAR # noqa: F401 from .instant import Instant # noqa: F401 from .period import Period # noqa: F401 diff --git a/openfisca_core/periods/_errors.py b/openfisca_core/periods/_errors.py index b412fffda0..3d3e06936b 100644 --- a/openfisca_core/periods/_errors.py +++ b/openfisca_core/periods/_errors.py @@ -6,7 +6,6 @@ from ._units import DAY, MONTH, YEAR - LEARN_MORE = ( "Learn more about legal period formats in OpenFisca: " "." diff --git a/openfisca_core/periods/_funcs.py b/openfisca_core/periods/_funcs.py index 2431045dca..99a3f6a2bf 100644 --- a/openfisca_core/periods/_funcs.py +++ b/openfisca_core/periods/_funcs.py @@ -12,7 +12,7 @@ from ._config import INSTANT_PATTERN from ._errors import InstantFormatError, PeriodFormatError -from ._units import DAY, ETERNITY, MONTH, YEAR, UNIT_WEIGHTS +from ._units import DAY, ETERNITY, MONTH, UNIT_WEIGHTS, YEAR from .instant import Instant from .period import Period diff --git a/openfisca_core/periods/period.py b/openfisca_core/periods/period.py index 5edfde84ee..fe73c94e8c 100644 --- a/openfisca_core/periods/period.py +++ b/openfisca_core/periods/period.py @@ -6,7 +6,7 @@ import datetime from ._errors import DateUnitValueError -from ._units import DAY, MONTH, YEAR, ETERNITY, UNIT_WEIGHTS +from ._units import DAY, ETERNITY, MONTH, UNIT_WEIGHTS, YEAR from .instant import Instant diff --git a/setup.cfg b/setup.cfg index dfa559dd75..71851433ae 100644 --- a/setup.cfg +++ b/setup.cfg @@ -54,8 +54,5 @@ ignore_errors = True [mypy-openfisca_core.holders.tests.*] ignore_errors = True -[mypy-openfisca_core.periods.tests.*] -ignore_errors = True - [mypy-openfisca_core.scripts.*] ignore_errors = True From baad937a0559a2977a3360228946d14c1aa734ee Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Wed, 14 Dec 2022 12:20:14 +0100 Subject: [PATCH 63/70] Encapsulate periods --- openfisca_core/holders/holder.py | 21 +++++----- openfisca_core/periods/__init__.py | 10 ++--- openfisca_core/periods/_errors.py | 5 +-- openfisca_core/periods/_funcs.py | 10 ++--- openfisca_core/periods/typing.py | 40 +++++++++++++++++++ openfisca_core/populations/population.py | 3 +- .../taxbenefitsystems/tax_benefit_system.py | 8 ++-- openfisca_core/types/__init__.py | 12 +----- openfisca_core/types/_data.py | 4 +- openfisca_core/types/_domain.py | 21 ++-------- openfisca_core/variables/variable.py | 3 +- setup.cfg | 8 ++-- 12 files changed, 80 insertions(+), 65 deletions(-) create mode 100644 openfisca_core/periods/typing.py diff --git a/openfisca_core/holders/holder.py b/openfisca_core/holders/holder.py index 18784b0b91..8d29106acf 100644 --- a/openfisca_core/holders/holder.py +++ b/openfisca_core/holders/holder.py @@ -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 @@ -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. @@ -211,6 +208,10 @@ def set_input( """ 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.', diff --git a/openfisca_core/periods/__init__.py b/openfisca_core/periods/__init__.py index 5185fc9110..63421594be 100644 --- a/openfisca_core/periods/__init__.py +++ b/openfisca_core/periods/__init__.py @@ -27,8 +27,8 @@ """ -from ._config import INSTANT_PATTERN # noqa: F401 -from ._funcs import build_instant, build_period, key_period_size, parse_period # noqa: F401 -from ._units import DAY, ETERNITY, MONTH, UNIT_WEIGHTS, YEAR # noqa: F401 -from .instant import Instant # noqa: F401 -from .period import Period # noqa: F401 +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 diff --git a/openfisca_core/periods/_errors.py b/openfisca_core/periods/_errors.py index 3d3e06936b..45e6e41c17 100644 --- a/openfisca_core/periods/_errors.py +++ b/openfisca_core/periods/_errors.py @@ -2,9 +2,8 @@ from typing import Any -from openfisca_core import types - from ._units import DAY, MONTH, YEAR +from .typing import Instant LEARN_MORE = ( "Learn more about legal period formats in OpenFisca: " @@ -38,7 +37,7 @@ class InstantTypeError(TypeError): def __init__(self, value: Any) -> None: super().__init__( f"Invalid instant: {value} of type {type(value)}, expecting an " - f"{type(types.Instant)}. {LEARN_MORE}" + f"{type(Instant)}. {LEARN_MORE}" ) diff --git a/openfisca_core/periods/_funcs.py b/openfisca_core/periods/_funcs.py index 99a3f6a2bf..4ba8452cd2 100644 --- a/openfisca_core/periods/_funcs.py +++ b/openfisca_core/periods/_funcs.py @@ -8,8 +8,6 @@ from pendulum.datetime import Date from pendulum.parsing import ParserError -from openfisca_core import types - from ._config import INSTANT_PATTERN from ._errors import InstantFormatError, PeriodFormatError from ._units import DAY, ETERNITY, MONTH, UNIT_WEIGHTS, YEAR @@ -19,7 +17,7 @@ UNIT_MAPPING = {1: "year", 2: "month", 3: "day"} -def build_instant(value: Any) -> Optional[types.Instant]: +def build_instant(value: Any) -> Optional[Instant]: """Build a new instant, aka a triple of integers (year, month, day). Args: @@ -92,7 +90,7 @@ def build_instant(value: Any) -> Optional[types.Instant]: return Instant(instant) -def build_period(value: Any) -> types.Period: +def build_period(value: Any) -> Period: """Build a new period, aka a triple (unit, start_instant, size). Args: @@ -199,7 +197,7 @@ def build_period(value: Any) -> types.Period: return Period((unit, base_period.start, size)) -def key_period_size(period: types.Period) -> str: +def key_period_size(period: Period) -> str: """Define a key in order to sort periods by length. It uses two aspects: first, ``unit``, then, ``size``. @@ -228,7 +226,7 @@ def key_period_size(period: types.Period) -> str: return f"{UNIT_WEIGHTS[unit]}_{size}" -def parse_period(value: str) -> Optional[types.Period]: +def parse_period(value: str) -> Optional[Period]: """Parse periods respecting the ISO format. Args: diff --git a/openfisca_core/periods/typing.py b/openfisca_core/periods/typing.py new file mode 100644 index 0000000000..93e0afe381 --- /dev/null +++ b/openfisca_core/periods/typing.py @@ -0,0 +1,40 @@ +# pylint: disable=missing-class-docstring,missing-function-docstring + +from __future__ import annotations + +import typing_extensions +from typing import Any +from typing_extensions import Protocol + +import abc + + +@typing_extensions.runtime_checkable +class Instant(Protocol): + @property + @abc.abstractmethod + def date(self) -> Any: ... + + @abc.abstractmethod + def offset(self, offset: Any, unit: Any) -> Any: ... + + +@typing_extensions.runtime_checkable +class Period(Protocol): + @abc.abstractmethod + def __iter__(self) -> Any: ... + + @property + @abc.abstractmethod + def unit(self) -> Any: ... + + @property + @abc.abstractmethod + def start(self) -> Any: ... + + @property + @abc.abstractmethod + def stop(self) -> Any: ... + + @abc.abstractmethod + def offset(self, offset: Any, unit: Any = None) -> Any: ... diff --git a/openfisca_core/populations/population.py b/openfisca_core/populations/population.py index edf08044f0..76b4ecb516 100644 --- a/openfisca_core/populations/population.py +++ b/openfisca_core/populations/population.py @@ -10,7 +10,8 @@ from openfisca_core import periods, projectors from openfisca_core.holders import Holder, MemoryUsage from openfisca_core.projectors import Projector -from openfisca_core.types import Array, Entity, Period, Role, Simulation +from openfisca_core.periods.typing import Period +from openfisca_core.types import Array, Entity, Role, Simulation from . import config diff --git a/openfisca_core/taxbenefitsystems/tax_benefit_system.py b/openfisca_core/taxbenefitsystems/tax_benefit_system.py index 35a90262d9..c8ec25f5aa 100644 --- a/openfisca_core/taxbenefitsystems/tax_benefit_system.py +++ b/openfisca_core/taxbenefitsystems/tax_benefit_system.py @@ -384,7 +384,7 @@ def _get_baseline_parameters_at_instant(self, instant): @functools.lru_cache() def get_parameters_at_instant( self, - instant: Union[str, int, types.Period, types.Instant], + instant: Union[str, int, Period, Instant], ) -> Optional[types.ParameterNodeAtInstant]: """Get the parameters of the legislation at a given instant @@ -396,13 +396,13 @@ def get_parameters_at_instant( """ - key: Optional[types.Instant] + key: Optional[Instant] msg: str - if isinstance(instant, types.Instant): + if isinstance(instant, Instant): key = instant - elif isinstance(instant, types.Period): + elif isinstance(instant, Period): key = instant.start elif isinstance(instant, (str, int)): diff --git a/openfisca_core/types/__init__.py b/openfisca_core/types/__init__.py index 699133aecb..12d4c3935c 100644 --- a/openfisca_core/types/__init__.py +++ b/openfisca_core/types/__init__.py @@ -11,10 +11,8 @@ * :attr:`.Entity` * :attr:`.Formula` * :attr:`.Holder` - * :attr:`.Instant` * :attr:`.ParameterNodeAtInstant` * :attr:`.Params` - * :attr:`.Period` * :attr:`.Population` * :attr:`.Role`, * :attr:`.Simulation`, @@ -49,19 +47,13 @@ # Official Public API -from ._data import ( # noqa: F401 - Array, - ArrayLike, - ) - +from ._data import Array, ArrayLike # noqa: F401 from ._domain import ( # noqa: F401 Entity, Formula, Holder, - Instant, ParameterNodeAtInstant, Params, - Period, Population, Role, Simulation, @@ -75,10 +67,8 @@ "Entity", "Formula", "Holder", - "Instant", "ParameterNodeAtInstant", "Params", - "Period", "Population", "Role", "Simulation", diff --git a/openfisca_core/types/_data.py b/openfisca_core/types/_data.py index ff7066d43a..fdc1533dda 100644 --- a/openfisca_core/types/_data.py +++ b/openfisca_core/types/_data.py @@ -1,8 +1,8 @@ from typing import Sequence, TypeVar, Union -from nptyping import types, NDArray as Array - import numpy +from nptyping import NDArray as Array +from nptyping import types T = TypeVar("T", bool, bytes, float, int, object, str) diff --git a/openfisca_core/types/_domain.py b/openfisca_core/types/_domain.py index 7eed350a78..276e3e6d7f 100644 --- a/openfisca_core/types/_domain.py +++ b/openfisca_core/types/_domain.py @@ -1,12 +1,13 @@ from __future__ import annotations -import numpy import typing_extensions from typing import Any, Optional from typing_extensions import Protocol import abc +import numpy + class Entity(Protocol): """Entity protocol.""" @@ -37,7 +38,7 @@ class Formula(Protocol): def __call__( self, population: Population, - instant: Instant, + instant: Any, params: Params, ) -> numpy.ndarray: """Abstract method.""" @@ -55,20 +56,6 @@ def get_memory_usage(self) -> Any: """Abstract method.""" -@typing_extensions.runtime_checkable -class Instant(Protocol): - """Instant protocol.""" - - @property - @abc.abstractmethod - def date(self) -> Any: - """Abstract method.""" - - @abc.abstractmethod - def offset(self, offset: Any, unit: Any) -> Any: - """Abstract method.""" - - @typing_extensions.runtime_checkable class ParameterNodeAtInstant(Protocol): """ParameterNodeAtInstant protocol.""" @@ -78,7 +65,7 @@ class Params(Protocol): """Params protocol.""" @abc.abstractmethod - def __call__(self, instant: Instant) -> ParameterNodeAtInstant: + def __call__(self, instant: Any) -> ParameterNodeAtInstant: """Abstract method.""" diff --git a/openfisca_core/variables/variable.py b/openfisca_core/variables/variable.py index be2ea03aad..0fc14a58f6 100644 --- a/openfisca_core/variables/variable.py +++ b/openfisca_core/variables/variable.py @@ -14,7 +14,8 @@ from openfisca_core.entities import Entity from openfisca_core.indexed_enums import Enum, EnumArray from openfisca_core.periods import Period -from openfisca_core.types import Formula, Instant +from openfisca_core.periods.typing import Instant +from openfisca_core.types import Formula from . import config, helpers diff --git a/setup.cfg b/setup.cfg index 71851433ae..53fe85cbf1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -12,9 +12,10 @@ [flake8] extend-ignore = D hang-closing = true -ignore = E128,E251,F403,F405,E501,RST301,W503,W504 +ignore = E128,E251,F403,F405,E501,E704,RST301,W503,W504 in-place = true include-in-doctest = openfisca_core/commons openfisca_core/holders openfisca_core/periods openfisca_core/types +per-file-ignores = */typing.py:D101,D102,E704, */__init__.py:F401 rst-directives = attribute, deprecated, seealso, versionadded, versionchanged rst-roles = any, attr, class, exc, func, meth, mod, obj strictness = short @@ -48,10 +49,7 @@ ignore_missing_imports = True install_types = True non_interactive = True -[mypy-openfisca_core.commons.tests.*] -ignore_errors = True - -[mypy-openfisca_core.holders.tests.*] +[mypy-openfisca_core.*.tests.*] ignore_errors = True [mypy-openfisca_core.scripts.*] From 7a11ece47d6419f44f67bf4ecab164ddaba917bc Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Wed, 14 Dec 2022 12:23:32 +0100 Subject: [PATCH 64/70] Simplify dict --- openfisca_core/periods/_units.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/openfisca_core/periods/_units.py b/openfisca_core/periods/_units.py index fc243a6572..d3f6a04cfd 100644 --- a/openfisca_core/periods/_units.py +++ b/openfisca_core/periods/_units.py @@ -2,10 +2,4 @@ MONTH = "month" YEAR = "year" ETERNITY = "eternity" - -UNIT_WEIGHTS = { - DAY: 100, - MONTH: 200, - YEAR: 300, - ETERNITY: 400, - } +UNIT_WEIGHTS = {DAY: 100, MONTH: 200, YEAR: 300, ETERNITY: 400} From 69126fab17dd9509e840e7a16a28c8b1740884d8 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Wed, 14 Dec 2022 15:08:47 +0100 Subject: [PATCH 65/70] Remove assertion in periods.helpers --- openfisca_core/periods/__init__.py | 6 +++--- openfisca_core/periods/{_funcs.py => helpers.py} | 4 ++-- openfisca_core/periods/{instant.py => instant_.py} | 0 openfisca_core/periods/{period.py => period_.py} | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) rename openfisca_core/periods/{_funcs.py => helpers.py} (99%) rename openfisca_core/periods/{instant.py => instant_.py} (100%) rename openfisca_core/periods/{period.py => period_.py} (99%) diff --git a/openfisca_core/periods/__init__.py b/openfisca_core/periods/__init__.py index 63421594be..aa9f83a240 100644 --- a/openfisca_core/periods/__init__.py +++ b/openfisca_core/periods/__init__.py @@ -28,7 +28,7 @@ """ 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 +from .helpers import build_instant, build_period, key_period_size, parse_period +from .instant_ import Instant +from .period_ import Period diff --git a/openfisca_core/periods/_funcs.py b/openfisca_core/periods/helpers.py similarity index 99% rename from openfisca_core/periods/_funcs.py rename to openfisca_core/periods/helpers.py index 4ba8452cd2..1c719dd2c6 100644 --- a/openfisca_core/periods/_funcs.py +++ b/openfisca_core/periods/helpers.py @@ -11,8 +11,8 @@ from ._config import INSTANT_PATTERN from ._errors import InstantFormatError, PeriodFormatError from ._units import DAY, ETERNITY, MONTH, UNIT_WEIGHTS, YEAR -from .instant import Instant -from .period import Period +from .instant_ import Instant +from .period_ import Period UNIT_MAPPING = {1: "year", 2: "month", 3: "day"} diff --git a/openfisca_core/periods/instant.py b/openfisca_core/periods/instant_.py similarity index 100% rename from openfisca_core/periods/instant.py rename to openfisca_core/periods/instant_.py diff --git a/openfisca_core/periods/period.py b/openfisca_core/periods/period_.py similarity index 99% rename from openfisca_core/periods/period.py rename to openfisca_core/periods/period_.py index fe73c94e8c..2e0532db45 100644 --- a/openfisca_core/periods/period.py +++ b/openfisca_core/periods/period_.py @@ -7,7 +7,7 @@ from ._errors import DateUnitValueError from ._units import DAY, ETERNITY, MONTH, UNIT_WEIGHTS, YEAR -from .instant import Instant +from .instant_ import Instant class Period(Tuple[str, Instant, int]): From 996d98763cc169db69e8d791baa8f2b395f11382 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Wed, 14 Dec 2022 15:12:41 +0100 Subject: [PATCH 66/70] Avoid inf to respect period type --- openfisca_core/periods/_errors.py | 14 +++++++++++++- openfisca_core/periods/helpers.py | 16 +++++++--------- openfisca_core/periods/tests/test_funcs.py | 6 +++--- 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/openfisca_core/periods/_errors.py b/openfisca_core/periods/_errors.py index 45e6e41c17..f1ec62a44f 100644 --- a/openfisca_core/periods/_errors.py +++ b/openfisca_core/periods/_errors.py @@ -31,13 +31,25 @@ def __init__(self, value: Any) -> None: ) +class InstantValueError(ValueError): + """Raised when an instant's values are not valid.""" + + def __init__(self, value: Any) -> None: + super().__init__( + f"Invalid instant: '{value}' has a length of {len(value)}. " + "Instants are described using the 'YYYY-MM-DD' format, for " + "instance '2015-06-15', therefore their length has to be within " + f" the following range: 1 <= length <= 3. {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}" + f"{type(Instant)}, {type(tuple)}, or {type(list)}. {LEARN_MORE}" ) diff --git a/openfisca_core/periods/helpers.py b/openfisca_core/periods/helpers.py index 1c719dd2c6..1e9747e135 100644 --- a/openfisca_core/periods/helpers.py +++ b/openfisca_core/periods/helpers.py @@ -9,7 +9,7 @@ from pendulum.parsing import ParserError from ._config import INSTANT_PATTERN -from ._errors import InstantFormatError, PeriodFormatError +from ._errors import InstantFormatError, InstantValueError, PeriodFormatError from ._units import DAY, ETERNITY, MONTH, UNIT_WEIGHTS, YEAR from .instant_ import Instant from .period_ import Period @@ -29,6 +29,7 @@ def build_instant(value: Any) -> Optional[Instant]: Raises: InstantFormatError: When the arguments were invalid, like "2021-32-13". + InstantValueError: When the length is out of range. Examples: >>> build_instant(datetime.date(2021, 9, 16)) @@ -72,14 +73,11 @@ def build_instant(value: Any) -> Optional[Instant]: elif isinstance(value, int): instant = value, - elif isinstance(value, list): - assert 1 <= len(value) <= 3 - instant = tuple(value) + elif isinstance(value, (tuple, list)) and not 1 <= len(value) <= 3: + raise InstantValueError(value) else: - assert isinstance(value, tuple), value - assert 1 <= len(value) <= 3 - instant = value + instant = tuple(value) if len(instant) == 1: return Instant((instant[0], 1, 1)) @@ -110,7 +108,7 @@ def build_period(value: Any) -> Period: Period(('day', Instant((2021, 1, 1)), 1)) >>> build_period("eternity") - Period(('eternity', Instant((1, 1, 1)), inf)) + Period(('eternity', Instant((1, 1, 1)), 1)) >>> build_period(2021) Period(('year', Instant((2021, 1, 1)), 1)) @@ -142,7 +140,7 @@ def build_period(value: Any) -> Period: return Period((DAY, value, 1)) if value == "ETERNITY" or value == ETERNITY: - return Period(("eternity", build_instant(datetime.date.min), float("inf"))) + return Period(("eternity", build_instant(datetime.date.min), 1)) if isinstance(value, int): return Period((YEAR, Instant((value, 1, 1)), 1)) diff --git a/openfisca_core/periods/tests/test_funcs.py b/openfisca_core/periods/tests/test_funcs.py index b2fbd59cd8..7189c7c3ec 100644 --- a/openfisca_core/periods/tests/test_funcs.py +++ b/openfisca_core/periods/tests/test_funcs.py @@ -45,9 +45,9 @@ def test_build_instant_with_an_invalid_argument(arg, error): @pytest.mark.parametrize("arg, expected", [ - ["eternity", periods.Period((periods.ETERNITY, periods.Instant((1, 1, 1)), float("inf")))], - ["ETERNITY", periods.Period((periods.ETERNITY, periods.Instant((1, 1, 1)), float("inf")))], - [periods.ETERNITY, periods.Period((periods.ETERNITY, periods.Instant((1, 1, 1)), float("inf")))], + ["eternity", periods.Period((periods.ETERNITY, periods.Instant((1, 1, 1)), 1))], + ["ETERNITY", periods.Period((periods.ETERNITY, periods.Instant((1, 1, 1)), 1))], + [periods.ETERNITY, periods.Period((periods.ETERNITY, periods.Instant((1, 1, 1)), 1))], [periods.Instant((1, 1, 1)), periods.Period((periods.DAY, periods.Instant((1, 1, 1)), 1))], [periods.Period((periods.DAY, periods.Instant((1, 1, 1)), 365)), periods.Period((periods.DAY, periods.Instant((1, 1, 1)), 365))], [1000, periods.Period((periods.YEAR, periods.Instant((1000, 1, 1)), 1))], From f34b6a651122285bb946be9e70e6baf323207b0f Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Wed, 14 Dec 2022 15:35:57 +0100 Subject: [PATCH 67/70] Refactor offset --- openfisca_core/periods/_errors.py | 5 +- openfisca_core/periods/instant_.py | 86 +++++--------------- openfisca_core/periods/period_.py | 4 +- openfisca_core/periods/tests/test_instant.py | 13 ++- 4 files changed, 37 insertions(+), 71 deletions(-) diff --git a/openfisca_core/periods/_errors.py b/openfisca_core/periods/_errors.py index f1ec62a44f..43fc9e3e1a 100644 --- a/openfisca_core/periods/_errors.py +++ b/openfisca_core/periods/_errors.py @@ -3,7 +3,6 @@ from typing import Any from ._units import DAY, MONTH, YEAR -from .typing import Instant LEARN_MORE = ( "Learn more about legal period formats in OpenFisca: " @@ -49,7 +48,7 @@ class InstantTypeError(TypeError): def __init__(self, value: Any) -> None: super().__init__( f"Invalid instant: {value} of type {type(value)}, expecting an " - f"{type(Instant)}, {type(tuple)}, or {type(list)}. {LEARN_MORE}" + f"'Instant', 'tuple', or 'list'. {LEARN_MORE}" ) @@ -70,5 +69,5 @@ class OffsetTypeError(TypeError): def __init__(self, value: Any) -> None: super().__init__( f"Invalid offset: {value} of type {type(value)}, expecting an " - f"{type(int)}. {LEARN_MORE}" + f"'int'. {LEARN_MORE}" ) diff --git a/openfisca_core/periods/instant_.py b/openfisca_core/periods/instant_.py index 1aacd46dcb..e3a1e2fd81 100644 --- a/openfisca_core/periods/instant_.py +++ b/openfisca_core/periods/instant_.py @@ -2,6 +2,7 @@ from typing import Dict, Optional, Tuple, Union +from dateutil import relativedelta import calendar import datetime @@ -216,76 +217,31 @@ def offset(self, offset: Union[str, int], unit: str) -> Instant: if unit not in (DAY, MONTH, YEAR): raise DateUnitValueError(unit) - if offset == 'first-of': - if unit == MONTH: - day = 1 + if offset == "first-of" and unit == YEAR: + return Instant((year, 1, 1)) - elif unit == YEAR: - month = 1 - day = 1 + if offset == "first-of" and unit == MONTH: + return Instant((year, month, 1)) - elif offset == 'last-of': - if unit == MONTH: - day = calendar.monthrange(year, month)[1] + if offset == "last-of" and unit == YEAR: + return Instant((year, 12, 31)) - elif unit == YEAR: - month = 12 - day = 31 + if offset == "last-of" and unit == MONTH: + return Instant((year, month, calendar.monthrange(year, month)[1])) - else: - if not isinstance(offset, int): - raise OffsetTypeError(offset) + if not isinstance(offset, int): + raise OffsetTypeError(offset) - if unit == DAY: - day += offset + if unit == YEAR: + date = self.date + relativedelta.relativedelta(years = offset) + return Instant((date.year, date.month, date.day)) - if offset < 0: - while day < 1: - month -= 1 + if unit == MONTH: + date = self.date + relativedelta.relativedelta(months = offset) + return Instant((date.year, date.month, date.day)) - if month == 0: - year -= 1 - month = 12 + if unit == DAY: + date = self.date + relativedelta.relativedelta(days = offset) + return Instant((date.year, date.month, date.day)) - day += calendar.monthrange(year, month)[1] - - elif offset > 0: - month_last_day = calendar.monthrange(year, month)[1] - - while day > month_last_day: - month += 1 - - if month == 13: - year += 1 - month = 1 - - day -= month_last_day - month_last_day = calendar.monthrange(year, month)[1] - - elif unit == MONTH: - month += offset - - if offset < 0: - while month < 1: - year -= 1 - month += 12 - - elif offset > 0: - while month > 12: - year += 1 - month -= 12 - month_last_day = calendar.monthrange(year, month)[1] - - if day > month_last_day: - day = month_last_day - - elif unit == YEAR: - year += offset - - # Handle february month of leap year. - month_last_day = calendar.monthrange(year, month)[1] - - if day > month_last_day: - day = month_last_day - - return Instant((year, month, day)) + return self diff --git a/openfisca_core/periods/period_.py b/openfisca_core/periods/period_.py index 2e0532db45..7aad8f05f0 100644 --- a/openfisca_core/periods/period_.py +++ b/openfisca_core/periods/period_.py @@ -536,4 +536,6 @@ def offset( """ - return Period((self[0], self[1].offset(offset, self[0] if unit is None else unit), self[2])) + start = self[1].offset(offset, self[0] if unit is None else unit) + + return Period((self[0], start, self[2])) diff --git a/openfisca_core/periods/tests/test_instant.py b/openfisca_core/periods/tests/test_instant.py index 63265e2766..e4b76f4b6f 100644 --- a/openfisca_core/periods/tests/test_instant.py +++ b/openfisca_core/periods/tests/test_instant.py @@ -40,10 +40,8 @@ def test_to_date_with_an_invalid_argument(arg, error): @pytest.mark.parametrize("offset, unit, expected", [ ["first-of", periods.YEAR, periods.Instant((2020, 1, 1))], ["first-of", periods.MONTH, periods.Instant((2020, 2, 1))], - ["first-of", periods.DAY, periods.Instant((2020, 2, 29))], ["last-of", periods.YEAR, periods.Instant((2020, 12, 31))], ["last-of", periods.MONTH, periods.Instant((2020, 2, 29))], - ["last-of", periods.DAY, periods.Instant((2020, 2, 29))], [-3, periods.YEAR, periods.Instant((2017, 2, 28))], [-3, periods.MONTH, periods.Instant((2019, 11, 29))], [-3, periods.DAY, periods.Instant((2020, 2, 26))], @@ -54,3 +52,14 @@ def test_to_date_with_an_invalid_argument(arg, error): def test_offset(instant, offset, unit, expected): """Returns the expected ``Instant``.""" assert instant.offset(offset, unit) == expected + + +@pytest.mark.parametrize("offset, unit, expected", [ + ["first-of", periods.DAY, TypeError], + ["last-of", periods.DAY, TypeError], + ]) +def test_offset_with_an_invalid_offset(instant, offset, unit, expected): + """Raises ``OffsetTypeError`` when given an invalid offset.""" + + with pytest.raises(TypeError): + instant.offset(offset, unit) From 923c8a04eaf12461592b0ea16ea9d36bfbc28667 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Wed, 14 Dec 2022 19:45:07 +0100 Subject: [PATCH 68/70] Deprecate instant to date --- CHANGELOG.md | 14 ++++-- openfisca_core/periods/instant_.py | 51 ++++---------------- openfisca_core/periods/tests/test_instant.py | 28 ----------- 3 files changed, 20 insertions(+), 73 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 09b367ff5d..b39a93830c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,20 +4,26 @@ #### Breaking changes +- Deprecate `instant_date`. - 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`. +- Rename `instant` to `build_instant`. - 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`. +- Fix `openfisca_core.periods` doctests. +- Add typing to `openfisca_core.periods`. + +#### Bug fixes + +- Fixes incoherent dates, +- Fixes several race conditions, +- Fixes impossible `last-of` and `first-of` offsets. # 38.0.0 [#989](https://github.com/openfisca/openfisca-core/pull/989) diff --git a/openfisca_core/periods/instant_.py b/openfisca_core/periods/instant_.py index e3a1e2fd81..700d5dd2b7 100644 --- a/openfisca_core/periods/instant_.py +++ b/openfisca_core/periods/instant_.py @@ -1,12 +1,12 @@ from __future__ import annotations -from typing import Dict, Optional, Tuple, Union +from typing import Dict, Tuple, Union from dateutil import relativedelta import calendar import datetime -from ._errors import DateUnitValueError, InstantTypeError, OffsetTypeError +from ._errors import DateUnitValueError, OffsetTypeError from ._units import DAY, MONTH, YEAR @@ -74,44 +74,14 @@ def __repr__(self) -> str: return f"{Instant.__name__}({super(Instant, self).__repr__()})" def __str__(self) -> str: - string = Instant._strings.get(self) + try: + return Instant._strings[self] - if string is not None: - return string - - Instant._strings = {self: self.date.isoformat(), **Instant._strings} + except KeyError: + Instant._strings = {self: self.date.isoformat(), **Instant._strings} return str(self) - @staticmethod - def to_date(value: Optional[Instant]) -> Optional[datetime.date]: - """Returns the date representation of an ``Instant``. - - Args: - value (Any): - An ``instant-like`` object to get the date from. - - Returns: - None: When ``value`` is None. - datetime.date: Otherwise. - - Raises: - InstantTypeError: When ``value`` is not an ``Instant``. - - Examples: - >>> Instant.to_date(Instant((2021, 1, 1))) - datetime.date(2021, 1, 1) - - """ - - if value is None: - return None - - if isinstance(value, Instant): - return value.date - - raise InstantTypeError(value) - @property def year(self) -> int: """The ``year`` of the ``Instant``. @@ -174,12 +144,11 @@ def date(self) -> datetime.date: """ - date = Instant._dates.get(self) - - if date is not None: - return date + try: + return Instant._dates[self] - Instant._dates = {self: datetime.date(*self), **Instant._dates} + except KeyError: + Instant._dates = {self: datetime.date(*self), **Instant._dates} return self.date diff --git a/openfisca_core/periods/tests/test_instant.py b/openfisca_core/periods/tests/test_instant.py index e4b76f4b6f..dcbeba6d8e 100644 --- a/openfisca_core/periods/tests/test_instant.py +++ b/openfisca_core/periods/tests/test_instant.py @@ -1,5 +1,3 @@ -import datetime - import pytest from openfisca_core import periods @@ -11,32 +9,6 @@ def instant(): return periods.Instant((2020, 2, 29)) -@pytest.mark.parametrize("arg, expected", [ - [None, None], - [periods.Instant((1, 1, 1)), datetime.date(1, 1, 1)], - [periods.Instant((4, 2, 29)), datetime.date(4, 2, 29)], - ]) -def test_to_date(arg, expected): - """Returns the expected ``date``.""" - assert periods.Instant.to_date(arg) == expected - - -@pytest.mark.parametrize("arg, error", [ - [(1, 1, 1), TypeError], - [periods.Instant((-1, 1, 1)), ValueError], - [periods.Instant((1, -1, 1)), ValueError], - [periods.Instant((1, 1, -1)), ValueError], - [periods.Instant((1, 13, 1)), ValueError], - [periods.Instant((1, 1, 32)), ValueError], - [periods.Instant((1, 2, 29)), ValueError], - ]) -def test_to_date_with_an_invalid_argument(arg, error): - """Raises ``ValueError`` when given an invalid argument.""" - - with pytest.raises(error): - periods.Instant.to_date(arg) - - @pytest.mark.parametrize("offset, unit, expected", [ ["first-of", periods.YEAR, periods.Instant((2020, 1, 1))], ["first-of", periods.MONTH, periods.Instant((2020, 2, 1))], From 66224e566dfe3d87978a4adeb232479432d1f243 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Wed, 14 Dec 2022 21:20:24 +0100 Subject: [PATCH 69/70] Rename to --- CHANGELOG.md | 13 +- openfisca_core/periods/instant_.py | 36 ++--- openfisca_core/periods/period_.py | 146 ++++++++++-------- openfisca_core/periods/tests/test_period.py | 2 +- .../scripts/measure_performances.py | 2 +- openfisca_core/simulations/simulation.py | 4 +- .../simulations/simulation_builder.py | 2 +- openfisca_core/variables/variable.py | 2 +- tests/core/variables/test_annualize.py | 6 +- 9 files changed, 109 insertions(+), 104 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b39a93830c..84c278fc41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,22 +8,25 @@ - Deprecate `periods.intersect`. - Deprecate `periods.unit_weight`. - Deprecate `periods.unit_weights`. -- Rename `period` to `build_period`. -- Rename `instant` to `build_instant`. -- Refactor `Period.contains` as `Period.__contains__`. - Make `periods.parse_period` stricter (for example `2022-1` now fails). +- Refactor `Period.contains` as `Period.__contains__`. +- Rename `Period.get_subperiods` to `subperiods`. +- Rename `instant` to `build_instant`. +- Rename `period` to `build_period`. +- Transform `Instant.date` from property to method. +- Transform `Period.date` from property to method. #### Technical changes +- Add typing to `openfisca_core.periods`. - Document `openfisca_core.periods`. - Fix `openfisca_core.periods` doctests. -- Add typing to `openfisca_core.periods`. #### Bug fixes +- Fixes impossible `last-of` and `first-of` offsets. - Fixes incoherent dates, - Fixes several race conditions, -- Fixes impossible `last-of` and `first-of` offsets. # 38.0.0 [#989](https://github.com/openfisca/openfisca-core/pull/989) diff --git a/openfisca_core/periods/instant_.py b/openfisca_core/periods/instant_.py index 700d5dd2b7..1d84292ce7 100644 --- a/openfisca_core/periods/instant_.py +++ b/openfisca_core/periods/instant_.py @@ -1,10 +1,11 @@ from __future__ import annotations -from typing import Dict, Tuple, Union +from typing import Tuple, Union from dateutil import relativedelta import calendar import datetime +import functools from ._errors import DateUnitValueError, OffsetTypeError from ._units import DAY, MONTH, YEAR @@ -64,23 +65,12 @@ class Instant(Tuple[int, int, int]): """ - #: A cache with the ``datetime.date`` representation of the ``Instant``. - _dates: Dict[Instant, datetime.date] = {} - - #: A cache with the ``str`` representation of the ``Instant``. - _strings: Dict[Instant, str] = {} - def __repr__(self) -> str: return f"{Instant.__name__}({super(Instant, self).__repr__()})" + @functools.lru_cache(maxsize = None) def __str__(self) -> str: - try: - return Instant._strings[self] - - except KeyError: - Instant._strings = {self: self.date.isoformat(), **Instant._strings} - - return str(self) + return self.date().isoformat() @property def year(self) -> int: @@ -130,13 +120,13 @@ def day(self) -> int: return self[2] - @property + @functools.lru_cache(maxsize = None) def date(self) -> datetime.date: """The date representation of the ``Instant``. Example: >>> instant = Instant((2021, 10, 1)) - >>> instant.date + >>> instant.date() datetime.date(2021, 10, 1) Returns: @@ -144,13 +134,7 @@ def date(self) -> datetime.date: """ - try: - return Instant._dates[self] - - except KeyError: - Instant._dates = {self: datetime.date(*self), **Instant._dates} - - return self.date + return datetime.date(*self) def offset(self, offset: Union[str, int], unit: str) -> Instant: """Increments/decrements the given instant with offset units. @@ -202,15 +186,15 @@ def offset(self, offset: Union[str, int], unit: str) -> Instant: raise OffsetTypeError(offset) if unit == YEAR: - date = self.date + relativedelta.relativedelta(years = offset) + date = self.date() + relativedelta.relativedelta(years = offset) return Instant((date.year, date.month, date.day)) if unit == MONTH: - date = self.date + relativedelta.relativedelta(months = offset) + date = self.date() + relativedelta.relativedelta(months = offset) return Instant((date.year, date.month, date.day)) if unit == DAY: - date = self.date + relativedelta.relativedelta(days = offset) + date = self.date() + relativedelta.relativedelta(days = offset) return Instant((date.year, date.month, date.day)) return self diff --git a/openfisca_core/periods/period_.py b/openfisca_core/periods/period_.py index 7aad8f05f0..bd51fc3b09 100644 --- a/openfisca_core/periods/period_.py +++ b/openfisca_core/periods/period_.py @@ -124,24 +124,28 @@ def __str__(self) -> str: if month == 1: # civil year starting from january return str(year) + else: # rolling year - return f'{YEAR}:{year}-{month:02d}' + return f"{YEAR}:{year}-{month:02d}" + # simple month if unit == MONTH and size == 1: - return f'{year}-{month:02d}' + return f"{year}-{month:02d}" + # several civil years if unit == YEAR and month == 1: - return f'{unit}:{year}:{size}' + return f"{unit}:{year}:{size}" if unit == DAY: if size == 1: - return f'{year}-{month:02d}-{day:02d}' + return f"{year}-{month:02d}-{day:02d}" + else: - return f'{unit}:{year}-{month:02d}-{day:02d}:{size}' + return f"{unit}:{year}-{month:02d}-{day:02d}:{size}" # complex period - return f'{unit}:{year}-{month:02d}:{size}' + return f"{unit}:{year}-{month:02d}:{size}" def __contains__(self, other: object) -> bool: """Checks if a ``period`` contains another one. @@ -166,34 +170,6 @@ def __contains__(self, other: object) -> bool: return super().__contains__(other) - @property - def date(self) -> datetime.date: - """The date representation of the ``period``'s' start date. - - Returns: - A datetime.date. - - Raises: - ValueError: If the period's size is greater than 1. - - Examples: - >>> instant = Instant((2021, 10, 1)) - >>> period = Period((YEAR, instant, 1)) - >>> period.date - datetime.date(2021, 10, 1) - - >>> period = Period((YEAR, instant, 3)) - >>> period.date - Traceback (most recent call last): - ValueError: "date" is undefined for a period of size > 1: year:2021-10:3. - - """ - - if self.size != 1: - raise ValueError(f'"date" is undefined for a period of size > 1: {self}.') - - return self.start.date - @property def unit(self) -> str: """The ``unit`` of the ``Period``. @@ -230,7 +206,7 @@ def days(self) -> int: """ - return (self.stop.date - self.start.date).days + 1 + return (self.stop.date() - self.start.date()).days + 1 @property def size(self) -> int: @@ -309,7 +285,7 @@ def size_in_days(self) -> int: if unit in [MONTH, YEAR]: last_day = self.start.offset(length, unit).offset(-1, DAY) - return (last_day.date - self.start.date).days + 1 + return (last_day.date() - self.start.date()).days + 1 raise ValueError(f"Cannot calculate number of days in {unit}") @@ -337,6 +313,9 @@ def stop(self) -> Instant: Returns: An Instant. + Raises: + DateUnitValueError: If the period's unit isn't day, month or year. + Examples: >>> Period(("year", Instant((2012, 2, 29)), 1)).stop Instant((2013, 2, 28)) @@ -359,37 +338,49 @@ def stop(self) -> Instant: if size > 1: day += size - 1 month_last_day = calendar.monthrange(year, month)[1] + while day > month_last_day: month += 1 + if month == 13: year += 1 month = 1 + day -= month_last_day month_last_day = calendar.monthrange(year, month)[1] else: - if unit == 'month': + if unit == "month": month += size while month > 12: year += 1 month -= 12 else: - assert unit == 'year', f'Invalid unit: {unit} of type {type(unit)}' + if not unit == "year": + raise DateUnitValueError(unit) + year += size day -= 1 + if day < 1: month -= 1 + if month == 0: year -= 1 month = 12 + day += calendar.monthrange(year, month)[1] + else: month_last_day = calendar.monthrange(year, month)[1] + if day > month_last_day: month += 1 + if month == 13: year += 1 month = 1 + day -= month_last_day return Instant((year, month, day)) @@ -469,43 +460,32 @@ def first_day(self) -> Period: """ return Period((DAY, self.start, 1)) - def get_subperiods(self, unit: str) -> Sequence[Period]: - """Return the list of all the periods of unit ``unit``. - - Args: - unit: A string representing period's ``unit``. + def date(self) -> datetime.date: + """The date representation of the ``period``'s' start date. Returns: - A list of periods. + A datetime.date. Raises: - DateUnitValueError: If the ``unit`` is not a valid date unit. - ValueError: If the period's unit is smaller than the given unit. + ValueError: If the period's size is greater than 1. Examples: - >>> period = Period((YEAR, Instant((2021, 1, 1)), 1)) - >>> period.get_subperiods(MONTH) - [Period(('month', Instant((2021, 1, 1)), 1)),...2021, 12, 1)), 1))] + >>> instant = Instant((2021, 10, 1)) + >>> period = Period((YEAR, instant, 1)) + >>> period.date() + datetime.date(2021, 10, 1) - >>> period = Period((YEAR, Instant((2021, 1, 1)), 2)) - >>> period.get_subperiods(YEAR) - [Period(('year', Instant((2021, 1, 1)), 1)),...((2022, 1, 1)), 1))] + >>> period = Period((YEAR, instant, 3)) + >>> period.date() + Traceback (most recent call last): + ValueError: 'date' undefined for period size > 1: year:2021-10:3. """ - if UNIT_WEIGHTS[self.unit] < UNIT_WEIGHTS[unit]: - raise ValueError(f"Cannot subdivide {self.unit} into {unit}") - - if unit == YEAR: - return [self.this_year.offset(i, YEAR) for i in range(self.size)] + if self.size > 1: + raise ValueError(f"'date' undefined for period size > 1: {self}.") - if unit == MONTH: - return [self.first_month.offset(i, MONTH) for i in range(self.size_in_months)] - - if unit == DAY: - return [self.first_day.offset(i, DAY) for i in range(self.size_in_days)] - - raise DateUnitValueError(unit) + return self.start.date() def offset( self, @@ -539,3 +519,41 @@ def offset( start = self[1].offset(offset, self[0] if unit is None else unit) return Period((self[0], start, self[2])) + + def subperiods(self, unit: str) -> Sequence[Period]: + """Return the list of all the periods of unit ``unit``. + + Args: + unit: A string representing period's ``unit``. + + Returns: + A list of periods. + + Raises: + DateUnitValueError: If the ``unit`` is not a valid date unit. + ValueError: If the period's unit is smaller than the given unit. + + Examples: + >>> period = Period((YEAR, Instant((2021, 1, 1)), 1)) + >>> period.subperiods(MONTH) + [Period(('month', Instant((2021, 1, 1)), 1)),...2021, 12, 1)), 1))] + + >>> period = Period((YEAR, Instant((2021, 1, 1)), 2)) + >>> period.subperiods(YEAR) + [Period(('year', Instant((2021, 1, 1)), 1)),...((2022, 1, 1)), 1))] + + """ + + if UNIT_WEIGHTS[self.unit] < UNIT_WEIGHTS[unit]: + raise ValueError(f"Cannot subdivide {self.unit} into {unit}") + + if unit == YEAR: + return [self.this_year.offset(i, YEAR) for i in range(self.size)] + + if unit == MONTH: + return [self.first_month.offset(i, MONTH) for i in range(self.size_in_months)] + + if unit == DAY: + return [self.first_day.offset(i, DAY) for i in range(self.size_in_days)] + + raise DateUnitValueError(unit) diff --git a/openfisca_core/periods/tests/test_period.py b/openfisca_core/periods/tests/test_period.py index 5801740537..60c068c567 100644 --- a/openfisca_core/periods/tests/test_period.py +++ b/openfisca_core/periods/tests/test_period.py @@ -54,7 +54,7 @@ def test_subperiods(instant, period_unit, unit, start, cease, count): """Returns the expected subperiods.""" period = periods.Period((period_unit, instant, 3)) - subperiods = period.get_subperiods(unit) + subperiods = period.subperiods(unit) assert len(subperiods) == count assert subperiods[0] == periods.Period((unit, start, 1)) diff --git a/openfisca_core/scripts/measure_performances.py b/openfisca_core/scripts/measure_performances.py index 5e0e01a218..341e61e47e 100644 --- a/openfisca_core/scripts/measure_performances.py +++ b/openfisca_core/scripts/measure_performances.py @@ -106,7 +106,7 @@ def formula(self, simulation, period): if age_en_mois is not None: return age_en_mois // 12 birth = simulation.calculate('birth', period) - return (numpy.datetime64(period.date) - birth).astype('timedelta64[Y]') + return (numpy.datetime64(period.date()) - birth).astype('timedelta64[Y]') class dom_tom(Variable): diff --git a/openfisca_core/simulations/simulation.py b/openfisca_core/simulations/simulation.py index 2c207c93e2..a92686f984 100644 --- a/openfisca_core/simulations/simulation.py +++ b/openfisca_core/simulations/simulation.py @@ -185,7 +185,7 @@ def calculate_add(self, variable_name: str, period): return sum( self.calculate(variable_name, sub_period) - for sub_period in period.get_subperiods(variable.definition_period) + for sub_period in period.subperiods(variable.definition_period) ) def calculate_divide(self, variable_name: str, period): @@ -439,7 +439,7 @@ def set_input(self, variable_name: str, period, value): raise VariableNotFoundError(variable_name, self.tax_benefit_system) period = periods.build_period(period) - if ((variable.end is not None) and (period.start.date > variable.end)): + if ((variable.end is not None) and (period.start.date() > variable.end)): return self.get_holder(variable_name).set_input(period, value) diff --git a/openfisca_core/simulations/simulation_builder.py b/openfisca_core/simulations/simulation_builder.py index 71193296b3..615a6995a4 100644 --- a/openfisca_core/simulations/simulation_builder.py +++ b/openfisca_core/simulations/simulation_builder.py @@ -422,7 +422,7 @@ def finalize_variables_init(self, population): variable = holder.variable # TODO - this duplicates the check in Simulation.set_input, but # fixing that requires improving Simulation's handling of entities - if (variable.end is None) or (period_value.start.date <= variable.end): + if (variable.end is None) or (period_value.start.date() <= variable.end): holder.set_input(period_value, array) def raise_period_mismatch(self, entity, json, e): diff --git a/openfisca_core/variables/variable.py b/openfisca_core/variables/variable.py index 0fc14a58f6..32f14e05fa 100644 --- a/openfisca_core/variables/variable.py +++ b/openfisca_core/variables/variable.py @@ -348,7 +348,7 @@ def get_formula( if instant is None: return None - if self.end and instant.date > self.end: + if self.end and instant.date() > self.end: return None instant_str = str(instant) diff --git a/tests/core/variables/test_annualize.py b/tests/core/variables/test_annualize.py index 8479c09357..c1cb1bbb3f 100644 --- a/tests/core/variables/test_annualize.py +++ b/tests/core/variables/test_annualize.py @@ -47,7 +47,7 @@ def test_without_annualize(monthly_variable): yearly_sum = sum( person('monthly_variable', month) - for month in period.get_subperiods(MONTH) + for month in period.subperiods(MONTH) ) assert monthly_variable.calculation_count == 11 @@ -62,7 +62,7 @@ def test_with_annualize(monthly_variable): yearly_sum = sum( person('monthly_variable', month) - for month in period.get_subperiods(MONTH) + for month in period.subperiods(MONTH) ) assert monthly_variable.calculation_count == 0 @@ -77,7 +77,7 @@ def test_with_partial_annualize(monthly_variable): yearly_sum = sum( person('monthly_variable', month) - for month in period.get_subperiods(MONTH) + for month in period.subperiods(MONTH) ) assert monthly_variable.calculation_count == 11 From 3b3c27a97966036b687a4d9074a9637e6b8427ae Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Thu, 15 Dec 2022 12:56:28 +0100 Subject: [PATCH 70/70] Fix typing --- openfisca_core/periods/helpers.py | 6 +++--- openfisca_core/periods/instant_.py | 4 ++-- openfisca_core/periods/period_.py | 6 +++--- openfisca_core/types/_domain.py | 8 ++++---- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/openfisca_core/periods/helpers.py b/openfisca_core/periods/helpers.py index 1e9747e135..82849e99b7 100644 --- a/openfisca_core/periods/helpers.py +++ b/openfisca_core/periods/helpers.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Any, Optional +from typing import Any import datetime @@ -17,7 +17,7 @@ UNIT_MAPPING = {1: "year", 2: "month", 3: "day"} -def build_instant(value: Any) -> Optional[Instant]: +def build_instant(value: Any) -> Instant | None: """Build a new instant, aka a triple of integers (year, month, day). Args: @@ -224,7 +224,7 @@ def key_period_size(period: Period) -> str: return f"{UNIT_WEIGHTS[unit]}_{size}" -def parse_period(value: str) -> Optional[Period]: +def parse_period(value: str) -> Period | None: """Parse periods respecting the ISO format. Args: diff --git a/openfisca_core/periods/instant_.py b/openfisca_core/periods/instant_.py index 1d84292ce7..8a37854c1e 100644 --- a/openfisca_core/periods/instant_.py +++ b/openfisca_core/periods/instant_.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Tuple, Union +from typing import Tuple from dateutil import relativedelta import calendar @@ -136,7 +136,7 @@ def date(self) -> datetime.date: return datetime.date(*self) - def offset(self, offset: Union[str, int], unit: str) -> Instant: + def offset(self, offset: str | int, unit: str) -> Instant: """Increments/decrements the given instant with offset units. Args: diff --git a/openfisca_core/periods/period_.py b/openfisca_core/periods/period_.py index bd51fc3b09..ef4d5d29c4 100644 --- a/openfisca_core/periods/period_.py +++ b/openfisca_core/periods/period_.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Optional, Sequence, Tuple, Union +from typing import Sequence, Tuple import calendar import datetime @@ -489,8 +489,8 @@ def date(self) -> datetime.date: def offset( self, - offset: Union[str, int], - unit: Optional[str] = None, + offset: str | int, + unit: str | None = None, ) -> Period: """Increment (or decrement) the given period with offset units. diff --git a/openfisca_core/types/_domain.py b/openfisca_core/types/_domain.py index 276e3e6d7f..12574b3b9d 100644 --- a/openfisca_core/types/_domain.py +++ b/openfisca_core/types/_domain.py @@ -1,7 +1,7 @@ from __future__ import annotations import typing_extensions -from typing import Any, Optional +from typing import Any from typing_extensions import Protocol import abc @@ -27,7 +27,7 @@ def check_variable_defined_for_entity(self, variable_name: Any) -> None: def get_variable( self, variable_name: Any, check_existence: Any = ..., - ) -> Optional[Any]: + ) -> Any | None: """Abstract method.""" @@ -130,7 +130,7 @@ def calculate_divide(self, variable_name: Any, period: Any) -> Any: """Abstract method.""" @abc.abstractmethod - def get_population(self, plural: Optional[Any]) -> Any: + def get_population(self, plural: Any | None) -> Any: """Abstract method.""" @@ -143,7 +143,7 @@ class TaxBenefitSystem(Protocol): def get_variable( self, variable_name: Any, check_existence: Any = ..., - ) -> Optional[Any]: + ) -> Any | None: """Abstract method."""