diff --git a/openfisca_core/periods/__init__.py b/openfisca_core/periods/__init__.py index 2335f1792..35c59b16b 100644 --- a/openfisca_core/periods/__init__.py +++ b/openfisca_core/periods/__init__.py @@ -21,7 +21,6 @@ # # See: https://www.python.org/dev/peps/pep-0008/#imports -from . import types from ._errors import InstantError, ParserError, PeriodError from .config import ( INSTANT_PATTERN, @@ -52,27 +51,26 @@ __all__ = [ "DAY", - "DateUnit", "ETERNITY", "INSTANT_PATTERN", "ISOCALENDAR", "ISOFORMAT", + "MONTH", + "WEEK", + "WEEKDAY", + "YEAR", + "DateUnit", "Instant", "InstantError", - "MONTH", "ParserError", "Period", "PeriodError", - "WEEK", - "WEEKDAY", - "YEAR", "date_by_instant_cache", "instant", "instant_date", "key_period_size", "period", "str_by_instant_cache", - "types", "unit_weight", "unit_weights", "year_or_month_or_day_re", diff --git a/openfisca_core/periods/_parsers.py b/openfisca_core/periods/_parsers.py index 9973b890a..30e57b03b 100644 --- a/openfisca_core/periods/_parsers.py +++ b/openfisca_core/periods/_parsers.py @@ -6,7 +6,8 @@ import pendulum -from . import types as t +from openfisca_core import types as t + from ._errors import InstantError, ParserError, PeriodError from .date_unit import DateUnit from .instant_ import Instant diff --git a/openfisca_core/periods/config.py b/openfisca_core/periods/config.py index 4486a5caf..983162912 100644 --- a/openfisca_core/periods/config.py +++ b/openfisca_core/periods/config.py @@ -2,7 +2,7 @@ import pendulum -from . import types as t +from openfisca_core import types as t # Matches "2015", "2015-01", "2015-01-01" # Does not match "2015-13", "2015-12-32" diff --git a/openfisca_core/periods/date_unit.py b/openfisca_core/periods/date_unit.py index c66346c3c..793bd8c9c 100644 --- a/openfisca_core/periods/date_unit.py +++ b/openfisca_core/periods/date_unit.py @@ -4,7 +4,7 @@ from strenum import StrEnum -from . import types as t +from openfisca_core import types as t class DateUnitMeta(EnumMeta): diff --git a/openfisca_core/periods/helpers.py b/openfisca_core/periods/helpers.py index fab26c48a..5f6d2f1f6 100644 --- a/openfisca_core/periods/helpers.py +++ b/openfisca_core/periods/helpers.py @@ -7,7 +7,9 @@ import pendulum -from . import config, types as t +from openfisca_core import types as t + +from . import config from ._errors import InstantError, PeriodError from ._parsers import parse_instant, parse_period from .date_unit import DateUnit diff --git a/openfisca_core/periods/instant_.py b/openfisca_core/periods/instant_.py index f71dbb322..2aee4c892 100644 --- a/openfisca_core/periods/instant_.py +++ b/openfisca_core/periods/instant_.py @@ -2,7 +2,9 @@ import pendulum -from . import config, types as t +from openfisca_core import types as t + +from . import config from .date_unit import DateUnit diff --git a/openfisca_core/periods/period_.py b/openfisca_core/periods/period_.py index 00e833d86..f02e74a09 100644 --- a/openfisca_core/periods/period_.py +++ b/openfisca_core/periods/period_.py @@ -7,7 +7,9 @@ import pendulum -from . import helpers, types as t +from openfisca_core import types as t + +from . import helpers from .date_unit import DateUnit from .instant_ import Instant diff --git a/openfisca_core/periods/types.py b/openfisca_core/periods/types.py deleted file mode 100644 index 092509c62..000000000 --- a/openfisca_core/periods/types.py +++ /dev/null @@ -1,183 +0,0 @@ -# TODO(): Properly resolve metaclass types. -# https://github.com/python/mypy/issues/14033 - -from collections.abc import Sequence - -from openfisca_core.types import DateUnit, Instant, Period - -import re - -#: Matches "2015", "2015-01", "2015-01-01" but not "2015-13", "2015-12-32". -iso_format = re.compile(r"^\d{4}(-(?:0[1-9]|1[0-2])(-(?:0[1-9]|[12]\d|3[01]))?)?$") - -#: Matches "2015", "2015-W01", "2015-W53-1" but not "2015-W54", "2015-W10-8". -iso_calendar = re.compile(r"^\d{4}(-W(0[1-9]|[1-4][0-9]|5[0-3]))?(-[1-7])?$") - - -class _SeqIntMeta(type): - def __instancecheck__(self, arg: object) -> bool: - return ( - bool(arg) - and isinstance(arg, Sequence) - and all(isinstance(item, int) for item in arg) - ) - - -class SeqInt(list[int], metaclass=_SeqIntMeta): # type: ignore[misc] - """A sequence of integers. - - Examples: - >>> isinstance([1, 2, 3], SeqInt) - True - - >>> isinstance((1, 2, 3), SeqInt) - True - - >>> isinstance({1, 2, 3}, SeqInt) - False - - >>> isinstance([1, 2, "3"], SeqInt) - False - - >>> isinstance(1, SeqInt) - False - - >>> isinstance([], SeqInt) - False - - """ - - -class _InstantStrMeta(type): - def __instancecheck__(self, arg: object) -> bool: - return isinstance(arg, (ISOFormatStr, ISOCalendarStr)) - - -class InstantStr(str, metaclass=_InstantStrMeta): # type: ignore[misc] - """A string representing an instant in string format. - - Examples: - >>> isinstance("2015", InstantStr) - True - - >>> isinstance("2015-01", InstantStr) - True - - >>> isinstance("2015-W01", InstantStr) - True - - >>> isinstance("2015-W01-12", InstantStr) - False - - >>> isinstance("week:2015-W01:3", InstantStr) - False - - """ - - __slots__ = () - - -class _ISOFormatStrMeta(type): - def __instancecheck__(self, arg: object) -> bool: - return isinstance(arg, str) and bool(iso_format.match(arg)) - - -class ISOFormatStr(str, metaclass=_ISOFormatStrMeta): # type: ignore[misc] - """A string representing an instant in ISO format. - - Examples: - >>> isinstance("2015", ISOFormatStr) - True - - >>> isinstance("2015-01", ISOFormatStr) - True - - >>> isinstance("2015-01-01", ISOFormatStr) - True - - >>> isinstance("2015-13", ISOFormatStr) - False - - >>> isinstance("2015-W01", ISOFormatStr) - False - - """ - - __slots__ = () - - -class _ISOCalendarStrMeta(type): - def __instancecheck__(self, arg: object) -> bool: - return isinstance(arg, str) and bool(iso_calendar.match(arg)) - - -class ISOCalendarStr(str, metaclass=_ISOCalendarStrMeta): # type: ignore[misc] - """A string representing an instant in ISO calendar. - - Examples: - >>> isinstance("2015", ISOCalendarStr) - True - - >>> isinstance("2015-W01", ISOCalendarStr) - True - - >>> isinstance("2015-W11-7", ISOCalendarStr) - True - - >>> isinstance("2015-W010", ISOCalendarStr) - False - - >>> isinstance("2015-01", ISOCalendarStr) - False - - """ - - __slots__ = () - - -class _PeriodStrMeta(type): - def __instancecheck__(self, arg: object) -> bool: - return ( - isinstance(arg, str) - and ":" in arg - and isinstance(arg.split(":")[1], InstantStr) - ) - - -class PeriodStr(str, metaclass=_PeriodStrMeta): # type: ignore[misc] - """A string representing a period. - - Examples: - >>> isinstance("year", PeriodStr) - False - - >>> isinstance("2015", PeriodStr) - False - - >>> isinstance("year:2015", PeriodStr) - True - - >>> isinstance("month:2015-01", PeriodStr) - True - - >>> isinstance("weekday:2015-W01-1:365", PeriodStr) - True - - >>> isinstance("2015-W01:1", PeriodStr) - False - - """ - - __slots__ = () - - -__all__ = [ - "DateUnit", - "ISOCalendarStr", - "ISOFormatStr", - "Instant", - "InstantStr", - "Period", - "PeriodStr", - "SeqInt", -] diff --git a/openfisca_core/types.py b/openfisca_core/types.py index f02df2801..e40148544 100644 --- a/openfisca_core/types.py +++ b/openfisca_core/types.py @@ -7,6 +7,7 @@ import abc import enum +import re import numpy import pendulum @@ -25,7 +26,7 @@ #: Generic covariant type var. _T_co = TypeVar("_T_co", covariant=True) -# Commons +# Arrays #: Type var for numpy arrays. _N_co = TypeVar("_N_co", covariant=True, bound="DTypeGeneric") @@ -60,6 +61,8 @@ #: Type alias for an array of generic objects. VarArray: TypeAlias = Array[VarDType] +# Arrays-like + #: Type var for array-like objects. _L = TypeVar("_L") @@ -93,6 +96,22 @@ #: Type for "generic" arrays. DTypeGeneric: TypeAlias = numpy.generic +# TODO(): Properly resolve metaclass types. +# https://github.com/python/mypy/issues/14033 + + +class _SeqIntMeta(type): + def __instancecheck__(self, arg: object, /) -> bool: + return ( + bool(arg) + and isinstance(arg, Sequence) + and all(isinstance(item, int) for item in arg) + ) + + +class SeqInt(list[int], metaclass=_SeqIntMeta): ... # type: ignore[misc] + + # Entities #: For example "person". @@ -215,22 +234,65 @@ def __getitem__( # Periods -#: For example "2000-01". -InstantStr = NewType("InstantStr", str) +#: Matches "2015", "2015-01", "2015-01-01" but not "2015-13", "2015-12-32". +iso_format = re.compile(r"^\d{4}(-(?:0[1-9]|1[0-2])(-(?:0[1-9]|[12]\d|3[01]))?)?$") + +#: Matches "2015", "2015-W01", "2015-W53-1" but not "2015-W54", "2015-W10-8". +iso_calendar = re.compile(r"^\d{4}(-W(0[1-9]|[1-4][0-9]|5[0-3]))?(-[1-7])?$") + +#: For example 2020. +InstantInt = NewType("InstantInt", int) #: For example 2020. PeriodInt = NewType("PeriodInt", int) -#: For example "1:2000-01-01:day". -PeriodStr = NewType("PeriodStr", str) + +class _InstantStrMeta(type): + def __instancecheck__(self, arg: object) -> bool: + return isinstance(arg, (ISOFormatStr, ISOCalendarStr)) + + +class InstantStr(str, metaclass=_InstantStrMeta): # type: ignore[misc] + __slots__ = () + + +class _ISOFormatStrMeta(type): + def __instancecheck__(self, arg: object) -> bool: + return isinstance(arg, str) and bool(iso_format.match(arg)) + + +class ISOFormatStr(str, metaclass=_ISOFormatStrMeta): # type: ignore[misc] + __slots__ = () + + +class _ISOCalendarStrMeta(type): + def __instancecheck__(self, arg: object) -> bool: + return isinstance(arg, str) and bool(iso_calendar.match(arg)) + + +class ISOCalendarStr(str, metaclass=_ISOCalendarStrMeta): # type: ignore[misc] + __slots__ = () + + +class _PeriodStrMeta(type): + def __instancecheck__(self, arg: object) -> bool: + return ( + isinstance(arg, str) + and ":" in arg + and isinstance(arg.split(":")[1], InstantStr) + ) + + +class PeriodStr(str, metaclass=_PeriodStrMeta): # type: ignore[misc] + __slots__ = () class Container(Protocol[_T_co]): - def __contains__(self, item: object, /) -> bool: ... + def __contains__(self, __item: object, /) -> bool: ... class Indexable(Protocol[_T_co]): - def __getitem__(self, index: int, /) -> _T_co: ... + def __getitem__(self, __index: int, /) -> _T_co: ... class DateUnit(Container[str], Protocol): @@ -246,9 +308,9 @@ def month(self, /) -> int: ... def day(self, /) -> int: ... @property def date(self, /) -> pendulum.Date: ... - def __lt__(self, other: object, /) -> bool: ... - def __le__(self, other: object, /) -> bool: ... - def offset(self, offset: str | int, unit: DateUnit, /) -> None | Instant: ... + def __lt__(self, __other: object, /) -> bool: ... + def __le__(self, __other: object, /) -> bool: ... + def offset(self, __offset: str | int, __unit: DateUnit, /) -> None | Instant: ... class Period(Indexable[Union[DateUnit, Instant, int]], Protocol): @@ -260,9 +322,14 @@ def start(self, /) -> Instant: ... def size(self, /) -> int: ... @property def stop(self, /) -> Instant: ... - def contains(self, other: Period, /) -> bool: ... - def offset(self, offset: str | int, unit: None | DateUnit = None, /) -> Period: ... + def contains(self, __other: Period, /) -> bool: ... + def offset( + self, __offset: str | int, __unit: None | DateUnit = None, / + ) -> Period: ... + +#: Type alias for a period-like object. +PeriodLike: TypeAlias = Union[Period, PeriodStr, PeriodInt] # Populations