Skip to content

Commit

Permalink
[Patch] Mypy compliant and imports (#42)
Browse files Browse the repository at this point in the history
* mypy compliant 

* conditional imports

* typing-extension min python version bump
  • Loading branch information
FBruzzesi authored Nov 10, 2023
1 parent 26ebc63 commit 32f3693
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 61 deletions.
18 changes: 10 additions & 8 deletions iso_week_date/_utils.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
from typing import Any, Callable, Type, TypeVar
import sys
from typing import Callable, Generic, Type, TypeVar, Union

try:
from typing import Self # type: ignore[attr-defined]
except ImportError:
from typing_extensions import Self # type: ignore[attr-defined]
if sys.version_info >= (3, 11):
from typing import Self
else:
from typing_extensions import Self


T = TypeVar("T")
R = TypeVar("R")


class classproperty:
class classproperty(Generic[T, R]):
"""
Decorator to create a class level property. It allows to define a property at the
class level, which can be accessed without creating an instance of the class.
Expand All @@ -30,11 +32,11 @@ def my_property(cls: Type):
```
"""

def __init__(self: Self, f: Callable[[Type[T]], Any]):
def __init__(self: Self, f: Callable[[Type[T]], R]) -> None:
"""Initialize classproperty."""
self.f = f

def __get__(self: Self, obj: T, owner: Type[T]) -> Any:
def __get__(self: Self, obj: Union[T, None], owner: Type[T]) -> R:
"""
Get the value of the class property.
Expand Down
44 changes: 24 additions & 20 deletions iso_week_date/base.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,28 @@
from __future__ import annotations

import re
import sys
from abc import ABC, abstractmethod
from datetime import date, datetime, timedelta
from enum import Enum
from typing import ClassVar, Generator, Literal, Type, TypeVar, Union, overload

from iso_week_date._utils import classproperty, format_err_msg, weeks_of_year
from iso_week_date.mixin import ComparatorMixin, ConverterMixin, ParserMixin
from iso_week_date.mixin import (
ComparatorMixin,
ConverterMixin,
IsoWeekProtocol,
ParserMixin,
)

try:
from typing import Self # type: ignore[attr-defined]
except ImportError:
from typing_extensions import Self # type: ignore[attr-defined]
if sys.version_info >= (3, 11):
from typing import Self
else:
from typing_extensions import Self

BaseIsoWeek_T = TypeVar("BaseIsoWeek_T", bound=Union[str, date, datetime, "BaseIsoWeek"])
BaseIsoWeek_T = TypeVar(
"BaseIsoWeek_T", str, date, datetime, "BaseIsoWeek", covariant=True
)


class InclusiveEnum(str, Enum):
Expand Down Expand Up @@ -95,12 +103,12 @@ def __str__(self: Self) -> str:
return self.value_

@classproperty
def _compact_pattern(cls) -> re.Pattern:
def _compact_pattern(cls: Type[IsoWeekProtocol]) -> re.Pattern:
"""Returns compiled compact pattern."""
return re.compile(cls._pattern.pattern.replace(")-(", ")("))

@classproperty
def _compact_format(cls) -> str:
def _compact_format(cls: Type[IsoWeekProtocol]) -> str:
"""Returns compact format as string."""
return cls._format.replace("-", "")

Expand Down Expand Up @@ -162,16 +170,12 @@ def quarter(self: Self) -> int:
return min((self.week - 1) // 13 + 1, 4)

@abstractmethod
def __add__(
self: Self, other: Union[int, timedelta]
) -> BaseIsoWeek: # pragma: no cover
def __add__(self: Self, other: Union[int, timedelta]) -> Self: # pragma: no cover
"""Implementation of addition operator."""
...

@overload
def __sub__(
self: Self, other: Union[int, timedelta]
) -> BaseIsoWeek: # pragma: no cover
def __sub__(self: Self, other: Union[int, timedelta]) -> Self: # pragma: no cover
"""Annotation for subtraction with `int` and `timedelta`"""
...

Expand All @@ -183,11 +187,11 @@ def __sub__(self: Self, other: BaseIsoWeek) -> int: # pragma: no cover
@abstractmethod
def __sub__(
self: Self, other: Union[int, timedelta, BaseIsoWeek]
) -> Union[int, BaseIsoWeek]: # pragma: no cover
) -> Union[int, Self]: # pragma: no cover
"""Implementation of subtraction operator."""
...

def __next__(self: Self) -> BaseIsoWeek:
def __next__(self: Self) -> Self:
"""Implementation of next operator."""
return self + 1

Expand All @@ -199,7 +203,7 @@ def range(
step: int = 1,
inclusive: Inclusive_T = "both",
as_str: bool = True,
) -> Generator[Union[str, BaseIsoWeek], None, None]:
) -> Generator[Union[str, Self], None, None]:
"""
Generates `BaseIsoWeek` (or `str`) between `start` and `end` values with given
`step`.
Expand Down Expand Up @@ -241,8 +245,8 @@ def range(
```
"""

_start: BaseIsoWeek = cls._cast(start)
_end: BaseIsoWeek = cls._cast(end)
_start = cls._cast(start)
_end = cls._cast(end)

if _start > _end:
raise ValueError(
Expand All @@ -266,7 +270,7 @@ def range(
range_start = 0 if inclusive in ("both", "left") else 1
range_end = _delta + 1 if inclusive in ("both", "right") else _delta

weeks_range: Generator[Union[str, BaseIsoWeek], None, None] = (
weeks_range: Generator[Union[str, Self], None, None] = (
(_start + i).to_string() if as_str else _start + i
for i in range(range_start, range_end, step)
)
Expand Down
16 changes: 12 additions & 4 deletions iso_week_date/isoweek.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
from __future__ import annotations

import sys
from datetime import date, datetime, timedelta
from typing import Any, Generator, Iterable, Tuple, TypeVar, Union, overload

from iso_week_date._patterns import ISOWEEK__DATE_FORMAT, ISOWEEK__FORMAT, ISOWEEK_PATTERN
from iso_week_date.base import BaseIsoWeek

try:
from typing import Self # type: ignore[attr-defined]
except ImportError:
from typing_extensions import Self # type: ignore[attr-defined]
if sys.version_info >= (3, 11):
from typing import Self
else:
from typing_extensions import Self

if sys.version_info >= (3, 12):
from typing import override
else:
from typing_extensions import override

IsoWeek_T = TypeVar("IsoWeek_T", date, datetime, str, "IsoWeek")

Expand Down Expand Up @@ -81,6 +87,7 @@ def nth(self: Self, n: int) -> date:

return self.days[n - 1]

@override
def to_datetime(self: Self, weekday: int = 1) -> datetime: # type: ignore[override]
"""
Converts `IsoWeek` to `datetime` object with the given weekday.
Expand Down Expand Up @@ -121,6 +128,7 @@ def to_datetime(self: Self, weekday: int = 1) -> datetime: # type: ignore[overr

return super().to_datetime(f"{self.value_}-{weekday}")

@override
def to_date(self: Self, weekday: int = 1) -> date: # type: ignore[override]
"""
Converts `IsoWeek` to `date` object with the given `weekday`.
Expand Down
9 changes: 5 additions & 4 deletions iso_week_date/isoweekdate.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import sys
from datetime import date, datetime, timedelta
from typing import Generator, TypeVar, Union

Expand All @@ -10,10 +11,10 @@
)
from iso_week_date.base import BaseIsoWeek

try:
from typing import Self # type: ignore[attr-defined]
except ImportError:
from typing_extensions import Self # type: ignore[attr-defined]
if sys.version_info >= (3, 11):
from typing import Self
else:
from typing_extensions import Self

IsoWeekDate_T = TypeVar("IsoWeekDate_T", date, datetime, str, "IsoWeekDate")

Expand Down
49 changes: 25 additions & 24 deletions iso_week_date/mixin.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
from __future__ import annotations

import re
import sys
from datetime import date, datetime, timedelta
from typing import Any, ClassVar, Protocol, Tuple, Type, TypeVar, runtime_checkable
from typing import Any, ClassVar, Protocol, Tuple, Type, TypeVar, Union, runtime_checkable

from iso_week_date._utils import classproperty, format_err_msg

try:
from typing import Self # type: ignore[attr-defined]
except ImportError:
from typing_extensions import Self # type: ignore[attr-defined]
if sys.version_info >= (3, 11):
from typing import Self
else:
from typing_extensions import Self


@runtime_checkable
Expand Down Expand Up @@ -49,10 +50,12 @@ def _compact_format(cls: Type[IsoWeekProtocol]) -> str:
...


IsoWeek_T = TypeVar("IsoWeek_T", str, date, datetime, "IsoWeekProtocol")
IsoWeek_T = TypeVar(
"IsoWeek_T", bound=Union[str, date, datetime, IsoWeekProtocol], contravariant=True
)


class ParserMixin:
class ParserMixin(IsoWeekProtocol):
"""
Mixin that implements `from_*` class methods to parse from:
Expand All @@ -68,7 +71,7 @@ class ParserMixin:
"""

@classmethod
def from_string(cls: Type, _str: str) -> Self:
def from_string(cls: Type[Self], _str: str) -> Self:
"""Parse a string object in `_pattern` format."""
if not isinstance(_str, str):
raise TypeError(f"Expected `str` type, found {type(_str)}.")
Expand Down Expand Up @@ -121,9 +124,7 @@ def from_today(cls: Type[Self]) -> Self: # pragma: no cover
return cls.from_date(date.today())

@classmethod
def from_values(
cls: Type[IsoWeekProtocol], year: int, week: int, weekday: int = 1
) -> Self:
def from_values(cls: Type[Self], year: int, week: int, weekday: int = 1) -> Self:
"""Parse year, week and weekday values to `_format` format."""
value = (
cls._format.replace("YYYY", str(year).zfill(4))
Expand All @@ -133,7 +134,7 @@ def from_values(
return cls(value)

@classmethod
def _cast(cls: Type[Self], value: IsoWeek_T) -> IsoWeekProtocol:
def _cast(cls: Type[Self], value: IsoWeek_T) -> Self:
"""
Automatically casts to `IsoWeekProtocol` type from the following possible types:
Expand Down Expand Up @@ -174,7 +175,7 @@ def _cast(cls: Type[Self], value: IsoWeek_T) -> IsoWeekProtocol:
)


class ConverterMixin:
class ConverterMixin(IsoWeekProtocol):
"""
Mixin that implements `to_*` instance methods to convert to the following types:
Expand All @@ -183,15 +184,15 @@ class ConverterMixin:
- `datetime`
"""

def to_string(self: IsoWeekProtocol) -> str:
def to_string(self: Self) -> str:
"""Returns as a string in the classical format."""
return self.value_

def to_compact(self: IsoWeekProtocol) -> str:
def to_compact(self: Self) -> str:
"""Returns as a string in the compact format."""
return self.value_.replace("-", "")

def to_datetime(self: IsoWeekProtocol, value: str) -> datetime:
def to_datetime(self: Self, value: str) -> datetime:
"""
Converts `value` to `datetime` object and adds the `offset_`.
Expand All @@ -209,20 +210,20 @@ def to_date(self: Self, value: str) -> date: # pragma: no cover
In general this is not always the case and we need to manipulate `value_`
attribute before passing it to `datetime.strptime` method.
"""
return self.to_datetime(value).date()
return self.to_datetime(value).date() # type: ignore

def to_values(self: IsoWeekProtocol) -> Tuple[int, ...]:
def to_values(self: Self) -> Tuple[int, ...]:
"""Converts `value_` to a tuple of integers (year, week, [weekday])."""
return tuple(int(v.replace("W", "")) for v in self.value_.split("-"))


class ComparatorMixin:
class ComparatorMixin(IsoWeekProtocol):
"""
Mixin that implements comparison operators ("==", "!=", "<", "<=", ">", ">=") between
two ISO Week objects.
"""

def __eq__(self: IsoWeekProtocol, other: Any) -> bool:
def __eq__(self: Self, other: Any) -> bool:
"""
Equality operator.
Expand Down Expand Up @@ -283,7 +284,7 @@ class CustomIsoWeek(IsoWeek):
"""
return not self.__eq__(other)

def __lt__(self: IsoWeekProtocol, other: IsoWeekProtocol) -> bool:
def __lt__(self: Self, other: Self) -> bool:
"""
Less than operator.
Expand Down Expand Up @@ -327,7 +328,7 @@ class CustomIsoWeek(IsoWeek):
f"comparison is supported only with other `{self.name}` objects"
)

def __le__(self: Self, other: IsoWeekProtocol) -> bool:
def __le__(self: Self, other: Self) -> bool:
"""
Less than or equal operator.
Expand Down Expand Up @@ -362,7 +363,7 @@ class CustomIsoWeek(IsoWeek):
"""
return self.__lt__(other) or self.__eq__(other)

def __gt__(self: Self, other: IsoWeekProtocol) -> bool:
def __gt__(self: Self, other: Self) -> bool:
"""Greater than operator.
Comparing two ISO Week objects is only possible if they have the same `offset_`.
Expand Down Expand Up @@ -396,7 +397,7 @@ class CustomIsoWeek(IsoWeek):
"""
return not self.__le__(other)

def __ge__(self: Self, other: IsoWeekProtocol) -> bool:
def __ge__(self: Self, other: Self) -> bool:
"""
Greater than or equal operator.
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ requires-python = ">=3.8"
authors = [{name = "Francesco Bruzzesi"}]

dependencies = [
"typing-extensions; python_version < '3.11'",
"typing-extensions; python_version < '3.12'",
]

classifiers = [
Expand Down

0 comments on commit 32f3693

Please sign in to comment.