Skip to content

Commit 01d68ea

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

File tree

6 files changed

+46
-39
lines changed

6 files changed

+46
-39
lines changed

.github/workflows/tox.yml

Lines changed: 3 additions & 3 deletions
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

Lines changed: 3 additions & 1 deletion
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

Lines changed: 38 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,23 @@
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
1112

1213

13-
async def null_callback(*args):
14+
async def null_callback(*args: tp.Any) -> tuple[tp.Any, ...]:
1415
return args
1516

1617

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

2021
@wraps(func)
21-
async def wrapper(*args, **kwargs):
22+
async def wrapper(*args: tp.Any, **kwargs: tp.Any) -> tp.Any:
2223
result = func(*args, **kwargs)
2324
if inspect.isawaitable(result):
2425
result = await result
@@ -30,70 +31,72 @@ async def wrapper(*args, **kwargs):
3031
class Cron(object):
3132
def __init__(
3233
self,
33-
spec,
34-
func=None,
35-
args=(),
36-
kwargs=None,
37-
start=False,
38-
uuid=None,
39-
loop=None,
40-
tz=None,
41-
):
34+
spec: str,
35+
func: tp.Optional[tp.Callable[..., tp.Union[tp.Any, tp.Awaitable[tp.Any]]]] = None,
36+
args: tuple[tp.Any, ...] = (),
37+
kwargs: tp.Optional[tp.Mapping[str, tp.Any]] = None,
38+
start: bool = False,
39+
uuid: tp.Optional[UUID] = None,
40+
loop: tp.Optional[asyncio.AbstractEventLoop] = None,
41+
tz: tp.Optional[zoneinfo.ZoneInfo] = None,
42+
) -> None:
4243
self.spec = spec
4344
if func is not None:
4445
kwargs = kwargs or {}
4546
self.func = func if not (args or kwargs) else partial(func, *args, **kwargs)
4647
else:
4748
self.func = null_callback
4849
self.tz = get_localzone() if tz is None else tz
49-
self.cron = wrap_func(self.func)
50+
self.cron: tp.Callable[..., tp.Awaitable[tp.Any]] = wrap_func(self.func)
5051
self.auto_start = start
5152
self.uuid = uuid if uuid is not None else uuid4()
52-
self.handle = self.future = self.cronsim = None
53+
self.handle = None
54+
self.future: tp.Optional[asyncio.Future] = None
55+
self.cronsim: tp.Optional[CronSim] = None
5356
self.loop = loop if loop is not None else asyncio.get_event_loop()
5457
if start and self.func is not null_callback:
5558
self.handle = self.loop.call_soon_threadsafe(self.start)
5659

57-
def start(self):
60+
def start(self) -> None:
5861
"""Start scheduling"""
5962
self.stop()
6063
self.initialize()
6164
self.handle = self.loop.call_at(self.get_next(), self.call_next)
6265

63-
def stop(self):
66+
def stop(self) -> None:
6467
"""Stop scheduling"""
6568
if self.handle is not None:
6669
self.handle.cancel()
6770
self.handle = self.future = self.cronsim = None
6871

69-
async def next(self, *args):
72+
async def next(self, *args: tp.Any) -> tp.Any:
7073
"""yield from .next()"""
7174
self.initialize()
7275
self.future = asyncio.Future(loop=self.loop)
7376
self.handle = self.loop.call_at(self.get_next(), self.call_func, *args)
7477
return await self.future
7578

76-
def initialize(self):
79+
def initialize(self) -> None:
7780
"""Initialize cronsim and related times"""
7881
if self.cronsim is None:
7982
self.time = time.time()
8083
self.datetime = datetime.now(self.tz)
8184
self.loop_time = self.loop.time()
8285
self.cronsim = CronSim(self.spec, self.datetime)
8386

84-
def get_next(self):
87+
def get_next(self) -> float:
8588
"""Return next iteration time related to loop time"""
8689
return self.loop_time + (next(self.cronsim).timestamp() - self.time)
8790

88-
def call_next(self):
91+
def call_next(self) -> None:
8992
"""Set next hop in the loop. Call task"""
9093
if self.handle is not None:
9194
self.handle.cancel()
9295
next_time = self.get_next()
9396
self.handle = self.loop.call_at(next_time, self.call_next)
9497
self.call_func()
9598

96-
def call_func(self, *args, **kwargs):
99+
def call_func(self, *args: tp.Any, **kwargs: tp.Any) -> None:
97100
"""Called. Take care of exceptions using gather"""
98101
"""Check the version of python installed"""
99102
if sys.version_info[0:2] >= (3, 10):
@@ -105,7 +108,7 @@ def call_func(self, *args, **kwargs):
105108
self.cron(*args, **kwargs), loop=self.loop, return_exceptions=True
106109
).add_done_callback(self.set_result)
107110

108-
def set_result(self, result):
111+
def set_result(self, result: asyncio.Future) -> None:
109112
"""Set future's result if needed (can be an exception).
110113
Else raise if needed."""
111114
result = result.result()[0]
@@ -118,22 +121,30 @@ def set_result(self, result):
118121
elif isinstance(result, Exception):
119122
raise result
120123

121-
def __call__(self, func):
124+
def __call__(self, func: tp.Callable[..., tp.Awaitable[tp.Any]]) -> 'Cron':
122125
"""Used as a decorator"""
123126
self.func = func
124127
self.cron = wrap_func(func)
125128
if self.auto_start:
126129
self.loop.call_soon_threadsafe(self.start)
127130
return self
128131

129-
def __str__(self):
132+
def __str__(self) -> str:
130133
return "{0.spec} {0.func}".format(self)
131134

132-
def __repr__(self):
135+
def __repr__(self) -> str:
133136
return "<Cron {0.spec} {0.func}>".format(self)
134137

135138

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

aiocron/__main__.py

Lines changed: 1 addition & 1 deletion
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 0 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)