Skip to content

Commit a94c8bf

Browse files
authored
Merge pull request #38 from Paillat-dev/master
Modernize project: Use pyproject.toml, uv package manager, update Python support, and revise examples
2 parents b396c78 + 22adfaf commit a94c8bf

15 files changed

+590
-548
lines changed

.github/workflows/tox.yml

+16-9
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,22 @@ jobs:
88
runs-on: ubuntu-latest
99
strategy:
1010
matrix:
11-
python: [3.7, 3.8, 3.9, "3.10", "3.11"]
11+
python-version: ["3.9", "3.10", "3.11", "3.12"]
1212

1313
steps:
14-
- uses: actions/checkout@v3
15-
- name: Setup Python
16-
uses: actions/setup-python@v4
14+
- uses: actions/checkout@v4
15+
16+
- name: Install uv
17+
uses: astral-sh/setup-uv@v3
1718
with:
18-
python-version: ${{ matrix.python }}
19-
- name: Install Tox and any other packages
20-
run: pip install tox
21-
- name: Run Tox
22-
run: tox -e py
19+
version: "0.4.18"
20+
enable-cache: true
21+
22+
- name: Set up Python ${{ matrix.python-version }}
23+
run: uv python install ${{ matrix.python-version }}
24+
25+
- name: Install the project
26+
run: uv sync --extra test
27+
28+
- name: Run tests
29+
run: uv run tox -e py

.gitignore

+3-1
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,12 @@ MANIFEST
4747

4848

4949

50-
5150
.DS_Store
5251

5352
# Jupyter Notebook
5453
.ipynb_checkpoints
5554

5655
.epg_data/*
56+
57+
# uv
58+
.python-version

MANIFEST.in

+5-8
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
1-
graft docs
2-
prune docs/_build
3-
prune .github/
1+
include LICENSE *.rst *.cfg *.ini pyproject.toml uv.lock .coveragerc
42
graft aiocron
53
graft tests
6-
include Pipfile Pipfile.lock *.rst *.cfg *.ini LICENSE
7-
global-exclude *.pyc
8-
global-exclude __pycache__
9-
include .coveragerc
4+
graft docs
5+
prune docs/_build
6+
prune .github
107
recursive-include examples *.py
11-
exclude .installed.cfg
8+
global-exclude *.pyc __pycache__

Pipfile

-18
This file was deleted.

Pipfile.lock

-368
This file was deleted.

aiocron/__init__.py

+25-17
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ async def null_callback(*args):
1616

1717
def wrap_func(func):
1818
"""wrap in a coroutine"""
19+
1920
@wraps(func)
2021
async def wrapper(*args, **kwargs):
2122
result = func(*args, **kwargs)
@@ -27,9 +28,18 @@ async def wrapper(*args, **kwargs):
2728

2829

2930
class Cron(object):
30-
31-
def __init__(self, spec, func=None, args=(), kwargs=None, start=False,
32-
uuid=None, loop=None, tz=None, croniter_kwargs=None):
31+
def __init__(
32+
self,
33+
spec,
34+
func=None,
35+
args=(),
36+
kwargs=None,
37+
start=False,
38+
uuid=None,
39+
loop=None,
40+
tz=None,
41+
croniter_kwargs=None,
42+
):
3343
self.spec = spec
3444
if func is not None:
3545
kwargs = kwargs or {}
@@ -63,7 +73,7 @@ async def next(self, *args):
6373
self.initialize()
6474
self.future = asyncio.Future(loop=self.loop)
6575
self.handle = self.loop.call_at(self.get_next(), self.call_func, *args)
66-
return (await self.future)
76+
return await self.future
6777

6878
def initialize(self):
6979
"""Initialize croniter and related times"""
@@ -72,9 +82,7 @@ def initialize(self):
7282
self.datetime = datetime.now(self.tz)
7383
self.loop_time = self.loop.time()
7484
self.croniter = croniter(
75-
self.spec,
76-
start_time=self.datetime,
77-
**self.croniter_kwargs
85+
self.spec, start_time=self.datetime, **self.croniter_kwargs
7886
)
7987

8088
def get_next(self):
@@ -92,16 +100,14 @@ def call_next(self):
92100
def call_func(self, *args, **kwargs):
93101
"""Called. Take care of exceptions using gather"""
94102
"""Check the version of python installed"""
95-
if (sys.version_info[0:2] >= (3, 10)):
103+
if sys.version_info[0:2] >= (3, 10):
96104
asyncio.gather(
97-
self.cron(*args, **kwargs),
98-
return_exceptions=True
99-
).add_done_callback(self.set_result)
105+
self.cron(*args, **kwargs), return_exceptions=True
106+
).add_done_callback(self.set_result)
100107
else:
101108
asyncio.gather(
102-
self.cron(*args, **kwargs),
103-
loop=self.loop, return_exceptions=True
104-
).add_done_callback(self.set_result)
109+
self.cron(*args, **kwargs), loop=self.loop, return_exceptions=True
110+
).add_done_callback(self.set_result)
105111

106112
def set_result(self, result):
107113
"""Set future's result if needed (can be an exception).
@@ -125,11 +131,13 @@ def __call__(self, func):
125131
return self
126132

127133
def __str__(self):
128-
return '{0.spec} {0.func}'.format(self)
134+
return "{0.spec} {0.func}".format(self)
129135

130136
def __repr__(self):
131-
return '<Cron {0.spec} {0.func}>'.format(self)
137+
return "<Cron {0.spec} {0.func}>".format(self)
132138

133139

134140
def crontab(spec, func=None, args=(), kwargs=None, start=True, loop=None, tz=None):
135-
return Cron(spec, func=func, args=args, kwargs=kwargs, start=start, loop=loop, tz=tz)
141+
return Cron(
142+
spec, func=func, args=args, kwargs=kwargs, start=start, loop=loop, tz=tz
143+
)

aiocron/__main__.py

+9-6
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,21 @@
99
def main():
1010
parser = argparse.ArgumentParser()
1111
parser.prog = "python -m aiocron"
12-
parser.add_argument("-n", type=int, default=1,
13-
help="loop N times. 0 for infinite loop", )
12+
parser.add_argument(
13+
"-n",
14+
type=int,
15+
default=1,
16+
help="loop N times. 0 for infinite loop",
17+
)
1418
parser.add_argument("crontab", help='quoted crontab. like "* * * * *"')
15-
parser.add_argument("command", nargs='+',
16-
help="shell command to run")
19+
parser.add_argument("command", nargs="+", help="shell command to run")
1720
args = parser.parse_args()
1821

1922
cron = args.crontab
2023
try:
2124
croniter(cron)
2225
except ValueError:
23-
parser.error('Invalid cron format')
26+
parser.error("Invalid cron format")
2427

2528
cmd = args.command
2629

@@ -43,5 +46,5 @@ def calback():
4346
pass
4447

4548

46-
if __name__ == '__main__': # pragma: no cover
49+
if __name__ == "__main__": # pragma: no cover
4750
main()

examples/simple.py

+21-21
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,49 @@
11
# -*- coding: utf-8 -*-
22
from aiocron import crontab
3-
from aiocron import asyncio
3+
import asyncio
44
import logging
55

6-
logging.basicConfig()
6+
logging.basicConfig(level=logging.DEBUG)
77

8-
loop = asyncio.get_event_loop()
8+
loop = asyncio.new_event_loop()
9+
asyncio.set_event_loop(loop)
910

10-
11-
@crontab('* * * * * */3')
11+
@crontab("* * * * * */3", loop=loop)
1212
def mycron():
13-
print('function')
14-
13+
print("function")
1514

16-
@crontab('* * * * * */2', start=False)
15+
@crontab("* * * * * */2", start=False, loop=loop)
1716
def mycron2(i):
1817
if i == 2:
1918
raise ValueError(i)
20-
return 'yielded function (%i)' % i
21-
19+
return f"yielded function ({i})"
2220

23-
@asyncio.coroutine
24-
def main():
25-
cron = crontab('* * * * * */2')
21+
async def main():
22+
cron = crontab("* * * * * */2", loop=loop)
2623
for i in range(3):
2724
try:
28-
yield from cron.next()
25+
await cron.next()
2926
except Exception:
3027
pass
3128
else:
32-
print('yielded (%i)' % i)
29+
print(f"yielded ({i})")
3330

3431
for i in range(3):
3532
try:
36-
res = yield from mycron2.next(i)
33+
res = await mycron2.next(i)
3734
except Exception as e:
3835
print(repr(e))
3936
else:
4037
print(res)
4138

42-
43-
loop.run_until_complete(main())
39+
if __name__ == "__main__":
40+
try:
41+
loop.run_until_complete(main())
42+
finally:
43+
loop.close()
4444

4545
"""
46-
Will print:
46+
Expected output (may vary slightly due to timing):
4747
4848
yielded (0)
4949
function
@@ -53,5 +53,5 @@ def main():
5353
yielded function (0)
5454
function
5555
yielded function (1)
56-
yielded function (2)
57-
"""
56+
ValueError(2)
57+
"""

examples/threaded.py

+19-13
Original file line numberDiff line numberDiff line change
@@ -6,34 +6,40 @@
66

77

88
class CronThread(threading.Thread):
9-
109
def __init__(self):
1110
super(CronThread, self).__init__()
11+
self.loop = None
1212
self.start()
13-
time.sleep(.1)
13+
time.sleep(0.1) # Give time for the loop to start
1414

1515
def run(self):
1616
self.loop = asyncio.new_event_loop()
1717
asyncio.set_event_loop(self.loop)
1818
self.loop.run_forever()
19-
self.loop.close()
2019

2120
def stop(self):
22-
self.loop.call_soon_threadsafe(self.loop.stop)
23-
self.join()
21+
if self.loop:
22+
self.loop.call_soon_threadsafe(self.loop.stop)
23+
self.join()
24+
self.loop.close()
2425

2526
def crontab(self, *args, **kwargs):
26-
kwargs['loop'] = self.loop
27+
kwargs["loop"] = self.loop
2728
return aiocron.crontab(*args, **kwargs)
2829

30+
2931
cron = CronThread()
3032

3133

32-
@cron.crontab('* * * * * *')
33-
@asyncio.coroutine
34-
def run():
35-
yield from asyncio.sleep(.1)
36-
print('It works')
34+
@cron.crontab("* * * * * *")
35+
async def run():
36+
await asyncio.sleep(0.1)
37+
print("It works")
38+
3739

38-
asyncio.get_event_loop().run_forever()
39-
cron.stop()
40+
# Run for a short time then stop
41+
try:
42+
time.sleep(5) # Let it run for 5 seconds
43+
finally:
44+
cron.stop()
45+
print("Cron stopped")

0 commit comments

Comments
 (0)