Skip to content

Commit d4464e8

Browse files
author
A.Shpak
committed
feat: add typing
1 parent 39f40de commit d4464e8

File tree

6 files changed

+55
-58
lines changed

6 files changed

+55
-58
lines changed

.github/workflows/tox.yml

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
name: tox
22

3-
on: [push, pull_request]
3+
on: [ push, pull_request ]
44

55
jobs:
66
tox:
77

88
runs-on: ubuntu-latest
99
strategy:
1010
matrix:
11-
python-version: ["3.9", "3.10", "3.11", "3.12"]
11+
python-version: [ "3.9", "3.10", "3.11", "3.12", "3.13" ]
1212

1313
steps:
1414
- uses: actions/checkout@v4
1515

1616
- name: Install uv
1717
uses: astral-sh/setup-uv@v3
1818
with:
19-
version: "0.4.18"
19+
version: "0.5"
2020
enable-cache: true
2121

2222
- name: Set up Python ${{ matrix.python-version }}

.gitignore

+3-1
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,6 @@ MANIFEST
5555
.epg_data/*
5656

5757
# uv
58-
.python-version
58+
.python-version
59+
60+
.venv/

aiocron/__init__.py

+39-27
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,24 @@
33
from datetime import datetime
44
from functools import wraps, partial
55
from tzlocal import get_localzone
6-
from uuid import uuid4
6+
from uuid import uuid4, UUID
77
import time
88
import asyncio
99
import sys
1010
import inspect
11+
import typing as tp
12+
import zoneinfo
1113

1214

13-
async def null_callback(*args):
15+
async def null_callback(*args: tp.Any) -> tp.Tuple[tp.Any, ...]:
1416
return args
1517

1618

17-
def wrap_func(func):
19+
def wrap_func(func: tp.Callable[..., tp.Union[tp.Any, tp.Awaitable[tp.Any]]]) -> tp.Callable[..., tp.Awaitable[tp.Any]]:
1820
"""wrap in a coroutine"""
1921

2022
@wraps(func)
21-
async def wrapper(*args, **kwargs):
23+
async def wrapper(*args: tp.Any, **kwargs: tp.Any) -> tp.Any:
2224
result = func(*args, **kwargs)
2325
if inspect.isawaitable(result):
2426
result = await result
@@ -30,70 +32,72 @@ async def wrapper(*args, **kwargs):
3032
class Cron(object):
3133
def __init__(
3234
self,
33-
spec,
34-
func=None,
35-
args=(),
36-
kwargs=None,
37-
start=False,
38-
uuid=None,
39-
loop=None,
40-
tz=None,
41-
):
35+
spec: str,
36+
func: tp.Optional[tp.Callable[..., tp.Union[tp.Any, tp.Awaitable[tp.Any]]]] = None,
37+
args: tp.Tuple[tp.Any, ...] = (),
38+
kwargs: tp.Optional[tp.Mapping[str, tp.Any]] = None,
39+
start: bool = False,
40+
uuid: tp.Optional[UUID] = None,
41+
loop: tp.Optional[asyncio.AbstractEventLoop] = None,
42+
tz: tp.Optional[zoneinfo.ZoneInfo] = None,
43+
) -> None:
4244
self.spec = spec
4345
if func is not None:
4446
kwargs = kwargs or {}
4547
self.func = func if not (args or kwargs) else partial(func, *args, **kwargs)
4648
else:
4749
self.func = null_callback
4850
self.tz = get_localzone() if tz is None else tz
49-
self.cron = wrap_func(self.func)
51+
self.cron: tp.Callable[..., tp.Awaitable[tp.Any]] = wrap_func(self.func)
5052
self.auto_start = start
5153
self.uuid = uuid if uuid is not None else uuid4()
52-
self.handle = self.future = self.cronsim = None
54+
self.handle = None
55+
self.future: tp.Optional[asyncio.Future] = None
56+
self.cronsim: tp.Optional[CronSim] = None
5357
self.loop = loop if loop is not None else asyncio.get_event_loop()
5458
if start and self.func is not null_callback:
5559
self.handle = self.loop.call_soon_threadsafe(self.start)
5660

57-
def start(self):
61+
def start(self) -> None:
5862
"""Start scheduling"""
5963
self.stop()
6064
self.initialize()
6165
self.handle = self.loop.call_at(self.get_next(), self.call_next)
6266

63-
def stop(self):
67+
def stop(self) -> None:
6468
"""Stop scheduling"""
6569
if self.handle is not None:
6670
self.handle.cancel()
6771
self.handle = self.future = self.cronsim = None
6872

69-
async def next(self, *args):
73+
async def next(self, *args: tp.Any) -> tp.Any:
7074
"""yield from .next()"""
7175
self.initialize()
7276
self.future = asyncio.Future(loop=self.loop)
7377
self.handle = self.loop.call_at(self.get_next(), self.call_func, *args)
7478
return await self.future
7579

76-
def initialize(self):
80+
def initialize(self) -> None:
7781
"""Initialize cronsim and related times"""
7882
if self.cronsim is None:
7983
self.time = time.time()
8084
self.datetime = datetime.now(self.tz)
8185
self.loop_time = self.loop.time()
8286
self.cronsim = CronSim(self.spec, self.datetime)
8387

84-
def get_next(self):
88+
def get_next(self) -> float:
8589
"""Return next iteration time related to loop time"""
8690
return self.loop_time + (next(self.cronsim).timestamp() - self.time)
8791

88-
def call_next(self):
92+
def call_next(self) -> None:
8993
"""Set next hop in the loop. Call task"""
9094
if self.handle is not None:
9195
self.handle.cancel()
9296
next_time = self.get_next()
9397
self.handle = self.loop.call_at(next_time, self.call_next)
9498
self.call_func()
9599

96-
def call_func(self, *args, **kwargs):
100+
def call_func(self, *args: tp.Any, **kwargs: tp.Any) -> None:
97101
"""Called. Take care of exceptions using gather"""
98102
"""Check the version of python installed"""
99103
if sys.version_info[0:2] >= (3, 10):
@@ -105,7 +109,7 @@ def call_func(self, *args, **kwargs):
105109
self.cron(*args, **kwargs), loop=self.loop, return_exceptions=True
106110
).add_done_callback(self.set_result)
107111

108-
def set_result(self, result):
112+
def set_result(self, result: asyncio.Future) -> None:
109113
"""Set future's result if needed (can be an exception).
110114
Else raise if needed."""
111115
result = result.result()[0]
@@ -118,22 +122,30 @@ def set_result(self, result):
118122
elif isinstance(result, Exception):
119123
raise result
120124

121-
def __call__(self, func):
125+
def __call__(self, func: tp.Callable[..., tp.Awaitable[tp.Any]]) -> 'Cron':
122126
"""Used as a decorator"""
123127
self.func = func
124128
self.cron = wrap_func(func)
125129
if self.auto_start:
126130
self.loop.call_soon_threadsafe(self.start)
127131
return self
128132

129-
def __str__(self):
133+
def __str__(self) -> str:
130134
return "{0.spec} {0.func}".format(self)
131135

132-
def __repr__(self):
136+
def __repr__(self) -> str:
133137
return "<Cron {0.spec} {0.func}>".format(self)
134138

135139

136-
def crontab(spec, func=None, args=(), kwargs=None, start=True, loop=None, tz=None):
140+
def crontab(
141+
spec: str,
142+
func: tp.Optional[tp.Callable[..., tp.Union[tp.Any, tp.Awaitable[tp.Any]]]] = None,
143+
args: tp.Tuple[tp.Any, ...] = (),
144+
kwargs: tp.Optional[tp.Mapping[str, tp.Any]] = None,
145+
start: bool = False,
146+
loop: tp.Optional[asyncio.AbstractEventLoop] = None,
147+
tz: tp.Optional[zoneinfo.ZoneInfo] = None,
148+
) -> Cron:
137149
return Cron(
138150
spec, func=func, args=args, kwargs=kwargs, start=start, loop=loop, tz=tz
139151
)

aiocron/__main__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import argparse
77

88

9-
def main():
9+
def main() -> None:
1010
parser = argparse.ArgumentParser()
1111
parser.prog = "python -m aiocron"
1212
parser.add_argument(

pyproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ classifiers = [
1818
"Programming Language :: Python :: 3.10",
1919
"Programming Language :: Python :: 3.11",
2020
"Programming Language :: Python :: 3.12",
21+
"Programming Language :: Python :: 3.13",
2122
"License :: OSI Approved :: MIT License",
2223
"Topic :: Software Development :: Libraries :: Python Modules",
2324
]
@@ -47,7 +48,6 @@ build-backend = "setuptools.build_meta"
4748
[tool.setuptools]
4849
packages = ["aiocron"]
4950

50-
5151
[tool.pytest.ini_options]
5252
addopts = "--cov aiocron --cov-report term-missing"
5353
testpaths = ["tests"]

uv.lock

+8-25
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)