Skip to content

Commit 539d10a

Browse files
committed
mypy checker added
1 parent 940e405 commit 539d10a

File tree

5 files changed

+58
-20
lines changed

5 files changed

+58
-20
lines changed

.pre-commit-config.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,13 @@ repos:
6262
- --line-length=120
6363
- --multi-line=9
6464
- --project=crontools
65+
- repo: https://github.com/pre-commit/mirrors-mypy
66+
rev: v1.0.1
67+
hooks:
68+
- id: mypy
69+
stages:
70+
- commit
71+
name: mypy
72+
pass_filenames: false
73+
args: ["--package", "crontools"]
74+
additional_dependencies: ["types-tzlocal"]

CHANGELOG.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ Changelog
66
------------------
77

88
- pipenv substituted by poetry
9+
- mypy checker added
910

1011

1112
0.1.5 (2022-04-24)

crontools/crontab.py

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22
import dataclasses as dc
33
import datetime as dt
44
import heapq
5-
from math import ceil
65
import operator as op
6+
from math import ceil
7+
from typing import Any, ClassVar, Dict, Generator, Generic, Iterable, Iterator, Optional, Tuple, Type, TypeVar, Union
8+
from typing import cast
9+
710
import tzlocal
8-
from typing import Any, ClassVar, Dict, Generic, Iterator, Iterable, Optional, Type, TypeVar, Tuple
911

1012
SENTINEL = object()
1113

@@ -83,19 +85,19 @@ def fromstr(cls, string: str) -> 'Range':
8385
begin, end, step = None, None, None
8486
string = string.strip()
8587

86-
interval, *maybe_step = string.split('/', maxsplit=1)
87-
if maybe_step:
88-
maybe_step = maybe_step[0]
88+
interval, *rem = string.split('/', maxsplit=1)
89+
if rem:
90+
maybe_step = rem[0]
8991
step = cls._parse_number(maybe_step)
9092

9193
if interval == '*':
9294
return cls(None, None, step)
9395

94-
begin, *maybe_end = interval.split('-', maxsplit=1)
96+
begin, *rem = interval.split('-', maxsplit=1)
9597
begin = cls.aliases.get(begin, begin) if cls.aliases else begin
9698
begin = cls._parse_number(begin)
97-
if maybe_end:
98-
maybe_end = maybe_end[0]
99+
if rem:
100+
maybe_end = rem[0]
99101
maybe_end = cls.aliases.get(maybe_end, maybe_end) if cls.aliases else maybe_end
100102

101103
end = cls._parse_number(maybe_end)
@@ -105,16 +107,18 @@ def fromstr(cls, string: str) -> 'Range':
105107
return cls(begin, end, step)
106108

107109
@classmethod
108-
def _parse_number(cls, value: str):
110+
def _parse_number(cls, value: Union[str, int]) -> int:
109111
try:
110-
value = int(value)
112+
parsed_value = int(value)
111113
except ValueError:
112114
raise ValueError(f"{cls.title} value must be of type int, got: {value}")
113115

114-
if not (cls.min_value <= value <= cls.max_value):
115-
raise ValueError(f"{cls.title} value must be of range [{cls.min_value}, {cls.max_value}], got: {value}")
116+
if not (cls.min_value <= parsed_value <= cls.max_value):
117+
raise ValueError(
118+
f"{cls.title} value must be of range [{cls.min_value}, {cls.max_value}], got: {parsed_value}",
119+
)
116120

117-
return value
121+
return parsed_value
118122

119123
@property
120124
def is_default(self) -> bool:
@@ -136,6 +140,8 @@ def iter(self, start_from: Optional[int] = None) -> Iterator[int]:
136140
begin = self.min_value
137141
end = self.max_value
138142
else:
143+
assert self.begin is not None
144+
139145
begin = self.begin
140146
end = self.begin if self.end is None else self.end
141147

@@ -263,9 +269,9 @@ def fromstr(cls, string: str) -> 'Field[RangeType]':
263269
:return: field
264270
"""
265271

266-
ranges = (cls.range_type.fromstr(item) for item in string.split(','))
272+
ranges = cast(Iterable[RangeType], (cls.range_type.fromstr(item) for item in string.split(',')))
267273

268-
return cls(ranges=tuple(sorted(ranges, key=lambda rng: rng.begin)))
274+
return cls(ranges=tuple(sorted(ranges, key=lambda rng: rng.begin if rng.begin is not None else rng.min_value)))
269275

270276
@property
271277
def is_default(self) -> bool:
@@ -313,23 +319,27 @@ def iter(self, year: Optional[int] = None, month: Optional[int] = None, start_fr
313319
year = now.year if year is None else year
314320
month = now.month if month is None else month
315321

322+
day_iter: Iterable[int]
316323
if self._weekday_field.is_default:
317324
day_iter = self._monthday_iter(year, month, start_from)
318325
elif self._monthday_field.is_default:
319326
day_iter = self._weekday_iter(year, month, start_from)
320327
else:
321-
day_iter = heapq.merge(self._monthday_iter(year, month, start_from), self._weekday_iter(year, month, start_from))
328+
day_iter = heapq.merge(
329+
self._monthday_iter(year, month, start_from),
330+
self._weekday_iter(year, month, start_from),
331+
)
322332

323333
return unique(day_iter)
324334

325-
def _monthday_iter(self, year: int, month: int, start_from: int = 1) -> Iterator[int]:
335+
def _monthday_iter(self, year: int, month: int, start_from: int = 1) -> Generator[int, None, None]:
326336
for day in self._monthday_field.iter(start_from=start_from):
327337
if day > calendar.monthrange(year, month)[1]:
328338
break
329339

330340
yield day
331341

332-
def _weekday_iter(self, year: int, month: int, start_day: int = 1) -> Iterator[int]:
342+
def _weekday_iter(self, year: int, month: int, start_day: int = 1) -> Generator[int, None, None]:
333343
curr_day = start_day
334344
curr_weekday = calendar.weekday(year, month, curr_day) + 1
335345
weekday_iter = self._weekday_field.iter(start_from=curr_weekday)
@@ -430,6 +440,7 @@ def parse(
430440

431441
fields_iter = iter(fields)
432442
now = (now or dt.datetime.now(tz=tz)).astimezone(tz)
443+
assert now.tzinfo is not None
433444

434445
return cls(
435446
second_field=SecondsField.fromstr(next(fields_iter)) if seconds_ext else SecondsField.fromstr('0'),
@@ -452,7 +463,8 @@ def __iter__(self) -> Iterator[dt.datetime]:
452463
for minute in self.minute_field:
453464
for second in self.second_field:
454465
yield dt.datetime(
455-
year=year, month=month, day=day, hour=hour, minute=minute, second=second, tzinfo=self.tz,
466+
year=year, month=month, day=day,
467+
hour=hour, minute=minute, second=second, tzinfo=self.tz,
456468
)
457469

458470
def iter(self, start_from: dt.datetime) -> Iterator[dt.datetime]:
@@ -508,7 +520,8 @@ def iter(self, start_from: dt.datetime) -> Iterator[dt.datetime]:
508520
continue
509521

510522
yield dt.datetime(
511-
year=year, month=month, day=day, hour=hour, minute=minute, second=second, tzinfo=self.tz,
523+
year=year, month=month, day=day,
524+
hour=hour, minute=minute, second=second, tzinfo=self.tz,
512525
)
513526

514527
first_run = False

crontools/py.typed

Whitespace-only changes.

pyproject.toml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ classifiers = [
1515
"Topic :: Software Development :: Libraries",
1616
"Topic :: Utilities"
1717
]
18+
include = ["crontools/py.typed"]
1819

1920
[tool.poetry.dependencies]
2021
python = "^3.7"
@@ -25,8 +26,21 @@ types-tzlocal = "^4.1.0"
2526
pytest = "^6.0"
2627
pytest-cov = "^2.0"
2728
freezegun = "^1.2"
29+
mypy = "^1.0.1"
2830
pre-commit = "^2.18.1"
2931

3032
[build-system]
3133
requires = ["poetry-core>=1.0.0"]
3234
build-backend = "poetry.core.masonry.api"
35+
36+
37+
[tool.mypy]
38+
allow_redefinition = true
39+
disallow_incomplete_defs = true
40+
disallow_any_generics = true
41+
disallow_untyped_decorators = true
42+
disallow_untyped_defs = true
43+
no_implicit_optional = true
44+
show_error_codes = true
45+
strict_equality = true
46+
warn_unused_ignores = true

0 commit comments

Comments
 (0)