CheeseAPI 1.3.2__tar.gz → 1.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {cheeseapi-1.3.2 → cheeseapi-1.4.0}/CheeseAPI/handle.py +32 -12
- {cheeseapi-1.3.2 → cheeseapi-1.4.0}/CheeseAPI/request.py +1 -1
- {cheeseapi-1.3.2 → cheeseapi-1.4.0}/CheeseAPI/response.py +2 -1
- {cheeseapi-1.3.2 → cheeseapi-1.4.0}/CheeseAPI/schedule.py +86 -23
- {cheeseapi-1.3.2 → cheeseapi-1.4.0}/CheeseAPI/server.py +4 -0
- {cheeseapi-1.3.2 → cheeseapi-1.4.0}/PKG-INFO +3 -2
- {cheeseapi-1.3.2 → cheeseapi-1.4.0}/pyproject.toml +4 -3
- {cheeseapi-1.3.2 → cheeseapi-1.4.0}/.gitignore +0 -0
- {cheeseapi-1.3.2 → cheeseapi-1.4.0}/CheeseAPI/__init__.py +0 -0
- {cheeseapi-1.3.2 → cheeseapi-1.4.0}/CheeseAPI/app.py +0 -0
- {cheeseapi-1.3.2 → cheeseapi-1.4.0}/CheeseAPI/cors.py +0 -0
- {cheeseapi-1.3.2 → cheeseapi-1.4.0}/CheeseAPI/exception.py +0 -0
- {cheeseapi-1.3.2 → cheeseapi-1.4.0}/CheeseAPI/file.py +0 -0
- {cheeseapi-1.3.2 → cheeseapi-1.4.0}/CheeseAPI/protocol.py +0 -0
- {cheeseapi-1.3.2 → cheeseapi-1.4.0}/CheeseAPI/route.py +0 -0
- {cheeseapi-1.3.2 → cheeseapi-1.4.0}/CheeseAPI/signal.py +0 -0
- {cheeseapi-1.3.2 → cheeseapi-1.4.0}/CheeseAPI/text.py +0 -0
- {cheeseapi-1.3.2 → cheeseapi-1.4.0}/CheeseAPI/validator.py +0 -0
- {cheeseapi-1.3.2 → cheeseapi-1.4.0}/CheeseAPI/websocket.py +0 -0
- {cheeseapi-1.3.2 → cheeseapi-1.4.0}/CheeseAPI/workspace.py +0 -0
- {cheeseapi-1.3.2 → cheeseapi-1.4.0}/LICENSE +0 -0
- {cheeseapi-1.3.2 → cheeseapi-1.4.0}/README.md +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import time, os, inspect, socket, multiprocessing, signal, http, ipaddress, datetime, traceback
|
|
1
|
+
import time, os, inspect, socket, multiprocessing, signal, http, ipaddress, datetime, traceback, threading
|
|
2
2
|
from typing import TYPE_CHECKING, Dict, Tuple, List
|
|
3
3
|
|
|
4
4
|
import asyncio, uvloop, setproctitle, websockets
|
|
@@ -10,6 +10,7 @@ from CheeseAPI.exception import Route_404_Exception, Route_405_Exception
|
|
|
10
10
|
if TYPE_CHECKING:
|
|
11
11
|
from CheeseAPI.app import App
|
|
12
12
|
from CheeseAPI.protocol import HttpProtocol, WebsocketProtocol
|
|
13
|
+
from CheeseAPI.schedule import ScheduleTask
|
|
13
14
|
|
|
14
15
|
class Handle:
|
|
15
16
|
def __init__(self, app: 'App'):
|
|
@@ -149,7 +150,6 @@ class Handle:
|
|
|
149
150
|
logger.error(f'''
|
|
150
151
|
{logger.encode(traceback.format_exc()[:-1])}''')
|
|
151
152
|
finally:
|
|
152
|
-
logger.destroy()
|
|
153
153
|
os.killpg(os.getpid(), signal.SIGKILL)
|
|
154
154
|
|
|
155
155
|
def worker_beforeStarting(self):
|
|
@@ -243,18 +243,38 @@ class Handle:
|
|
|
243
243
|
return
|
|
244
244
|
|
|
245
245
|
timer = datetime.datetime.now()
|
|
246
|
+
tasks: List[ScheduleTask] = []
|
|
246
247
|
|
|
247
248
|
for task in self._app.scheduler.tasks.values():
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
'
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
if task.
|
|
257
|
-
self._app.scheduler.
|
|
249
|
+
if task.inactive or task.expired:
|
|
250
|
+
continue
|
|
251
|
+
|
|
252
|
+
if task.mode == 'multiprocessing':
|
|
253
|
+
if task.key not in self._app.scheduler._taskHandlers:
|
|
254
|
+
self._app.scheduler._taskHandlers[task.key] = multiprocessing.Process(target = self._app.scheduler._processHandle, args = (task.key, ), name = f'{setproctitle.getproctitle()}:SchedulerTask:{task.key}', daemon = True)
|
|
255
|
+
self._app.scheduler._taskHandlers[task.key].start()
|
|
256
|
+
elif task.mode == 'threading':
|
|
257
|
+
if task.key not in self._app.scheduler._taskHandlers:
|
|
258
|
+
self._app.scheduler._taskHandlers[task.key] = threading.Thread(target = self._app.scheduler._processHandle, args = (task.key, ), name = f'{setproctitle.getproctitle()}:SchedulerTask:{task.key}', daemon = True)
|
|
259
|
+
self._app.scheduler._taskHandlers[task.key].start()
|
|
260
|
+
elif task.mode == 'asyncio':
|
|
261
|
+
triggeredTimer = task.startTimer + task.timer * task.total_repetition_num
|
|
262
|
+
|
|
263
|
+
if (self._timer < triggeredTimer < timer or self._timer > triggeredTimer + task.timer):
|
|
264
|
+
tasks.append(task)
|
|
265
|
+
|
|
266
|
+
for task in tasks:
|
|
267
|
+
self._app._managers_['schedules'][task.key] = {
|
|
268
|
+
**self._app._managers_['schedules'][task.key],
|
|
269
|
+
'total_repetition_num': task.total_repetition_num + 1,
|
|
270
|
+
'lastTimer': timer
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
await asyncio.gather(*[ task.fn() for task in tasks ])
|
|
274
|
+
|
|
275
|
+
for task in self._app.scheduler.tasks.values():
|
|
276
|
+
if task.expired and task.auto_remove:
|
|
277
|
+
self._app.scheduler.remove(task.key)
|
|
258
278
|
|
|
259
279
|
self._timer = timer
|
|
260
280
|
|
|
@@ -360,6 +360,7 @@ class BaseResponse:
|
|
|
360
360
|
'Transfer-Encoding': 'chunked',
|
|
361
361
|
**headers
|
|
362
362
|
}
|
|
363
|
+
self.headers['Content-Type'] += '; charset=utf-8'
|
|
363
364
|
self.body: str | bytes | Callable | AsyncIterator = self.status.description if body is None else body
|
|
364
365
|
self._transfering: bool = False
|
|
365
366
|
|
|
@@ -464,7 +465,7 @@ class JsonResponse(BaseResponse):
|
|
|
464
465
|
|
|
465
466
|
def __init__(self, body: dict | list = {}, status: http.HTTPStatus | int = http.HTTPStatus.OK, headers: Dict[str, str] = {}):
|
|
466
467
|
super().__init__(json.dumps(body), status, {
|
|
467
|
-
'Content-Type': 'application/json
|
|
468
|
+
'Content-Type': 'application/json',
|
|
468
469
|
**headers
|
|
469
470
|
})
|
|
470
471
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import uuid, datetime
|
|
2
|
-
from typing import TYPE_CHECKING, Callable, Dict, overload
|
|
1
|
+
import uuid, datetime, multiprocessing, threading, time
|
|
2
|
+
from typing import TYPE_CHECKING, Callable, Dict, overload, Literal
|
|
3
3
|
|
|
4
|
-
import dill
|
|
4
|
+
import dill, setproctitle
|
|
5
5
|
|
|
6
6
|
if TYPE_CHECKING:
|
|
7
7
|
from CheeseAPI.app import App
|
|
@@ -9,7 +9,7 @@ if TYPE_CHECKING:
|
|
|
9
9
|
class ScheduleTask:
|
|
10
10
|
def __init__(self, app: 'App', key: str):
|
|
11
11
|
self._app: 'App' = app
|
|
12
|
-
self.
|
|
12
|
+
self._key: str = key
|
|
13
13
|
|
|
14
14
|
def reset(self):
|
|
15
15
|
'''
|
|
@@ -21,6 +21,14 @@ class ScheduleTask:
|
|
|
21
21
|
'total_repetition_num': 0
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
@property
|
|
25
|
+
def key(self) -> str:
|
|
26
|
+
'''
|
|
27
|
+
【只读】
|
|
28
|
+
'''
|
|
29
|
+
|
|
30
|
+
return self._key
|
|
31
|
+
|
|
24
32
|
@property
|
|
25
33
|
def timer(self) -> datetime.timedelta:
|
|
26
34
|
'''
|
|
@@ -52,7 +60,7 @@ class ScheduleTask:
|
|
|
52
60
|
'''
|
|
53
61
|
自定义的开始时间。若未设置,则为当前时间。
|
|
54
62
|
|
|
55
|
-
若设置后`self.
|
|
63
|
+
若设置后`self.expired and self.auto_remove`,则该任务会被立刻删除。
|
|
56
64
|
'''
|
|
57
65
|
|
|
58
66
|
return self._app._managers_['schedules'][self.key]['startTimer']
|
|
@@ -64,7 +72,7 @@ class ScheduleTask:
|
|
|
64
72
|
'startTimer': value
|
|
65
73
|
}
|
|
66
74
|
|
|
67
|
-
if self.auto_remove and self.
|
|
75
|
+
if self.auto_remove and self.expired:
|
|
68
76
|
del self._app._managers_['schedules'][self.key]
|
|
69
77
|
|
|
70
78
|
@property
|
|
@@ -72,7 +80,7 @@ class ScheduleTask:
|
|
|
72
80
|
'''
|
|
73
81
|
任务过期后是否自动删除。
|
|
74
82
|
|
|
75
|
-
若设置后`self.
|
|
83
|
+
若设置后`self.expired and self.auto_remove`,则该任务会被立刻删除。
|
|
76
84
|
'''
|
|
77
85
|
|
|
78
86
|
return self._app._managers_['schedules'][self.key]['auto_remove']
|
|
@@ -84,7 +92,7 @@ class ScheduleTask:
|
|
|
84
92
|
'auto_remove': value
|
|
85
93
|
}
|
|
86
94
|
|
|
87
|
-
if self.auto_remove and self.
|
|
95
|
+
if self.auto_remove and self.expired:
|
|
88
96
|
del self._app._managers_['schedules'][self.key]
|
|
89
97
|
|
|
90
98
|
@property
|
|
@@ -102,15 +110,20 @@ class ScheduleTask:
|
|
|
102
110
|
'active': value
|
|
103
111
|
}
|
|
104
112
|
|
|
105
|
-
|
|
106
|
-
|
|
113
|
+
@property
|
|
114
|
+
def inactive(self) -> bool:
|
|
115
|
+
'''
|
|
116
|
+
【只读】 是否未激活。
|
|
117
|
+
'''
|
|
118
|
+
|
|
119
|
+
return not self._app._managers_['schedules'][self.key]['active']
|
|
107
120
|
|
|
108
121
|
@property
|
|
109
122
|
def expected_repetition_num(self) -> int:
|
|
110
123
|
'''
|
|
111
124
|
期望的重复次数;0代表无限次数。
|
|
112
125
|
|
|
113
|
-
若设置后`self.
|
|
126
|
+
若设置后`self.expired and self.auto_remove`,则该任务会被立刻删除。
|
|
114
127
|
'''
|
|
115
128
|
|
|
116
129
|
return self._app._managers_['schedules'][self.key]['expected_repetition_num']
|
|
@@ -122,8 +135,13 @@ class ScheduleTask:
|
|
|
122
135
|
'expected_repetition_num': value
|
|
123
136
|
}
|
|
124
137
|
|
|
125
|
-
|
|
126
|
-
|
|
138
|
+
@property
|
|
139
|
+
def mode(self) -> Literal['multiprocessing', 'threading', 'asyncio']:
|
|
140
|
+
'''
|
|
141
|
+
【只读】 运行的模式是进程、线程还是协程。
|
|
142
|
+
'''
|
|
143
|
+
|
|
144
|
+
return self._app._managers_['schedules'][self.key]['mode']
|
|
127
145
|
|
|
128
146
|
@property
|
|
129
147
|
def total_repetition_num(self) -> int:
|
|
@@ -144,7 +162,7 @@ class ScheduleTask:
|
|
|
144
162
|
return self.expected_repetition_num - self.total_repetition_num
|
|
145
163
|
|
|
146
164
|
@property
|
|
147
|
-
def
|
|
165
|
+
def unexpired(self) -> bool:
|
|
148
166
|
'''
|
|
149
167
|
【只读】 任务是否未过期。
|
|
150
168
|
'''
|
|
@@ -155,19 +173,56 @@ class ScheduleTask:
|
|
|
155
173
|
return self.startTimer + self.timer * (self.expected_repetition_num + 1) >= datetime.datetime.now()
|
|
156
174
|
|
|
157
175
|
@property
|
|
158
|
-
def
|
|
176
|
+
def expired(self) -> bool:
|
|
159
177
|
'''
|
|
160
178
|
【只读】 任务是否过期。
|
|
161
179
|
'''
|
|
162
180
|
|
|
163
|
-
return not self.
|
|
181
|
+
return not self.unexpired
|
|
182
|
+
|
|
183
|
+
@property
|
|
184
|
+
def lastTimer(self) -> datetime.datetime | None:
|
|
185
|
+
'''
|
|
186
|
+
【只读】 任务上一次的触发时间;`None`代表从未触发过。
|
|
187
|
+
'''
|
|
188
|
+
|
|
189
|
+
return self._app._managers_['schedules'][self.key]['lastTimer']
|
|
164
190
|
|
|
165
191
|
class Scheduler:
|
|
166
192
|
def __init__(self, app: 'App'):
|
|
167
193
|
self._app: 'App' = app
|
|
194
|
+
self._taskHandlers: Dict[str, multiprocessing.Process | threading.Thread] = {}
|
|
195
|
+
|
|
196
|
+
def _processHandle(self, key: str):
|
|
197
|
+
task: ScheduleTask = self._app.scheduler.tasks[key]
|
|
198
|
+
if task.mode == 'multiprocessing':
|
|
199
|
+
setproctitle.setproctitle(f'{setproctitle.getproctitle()}:SchedulerTask:{task.key}')
|
|
200
|
+
lastTimer = datetime.datetime.now()
|
|
201
|
+
|
|
202
|
+
while True:
|
|
203
|
+
task: ScheduleTask = self._app.scheduler.tasks.get(key)
|
|
204
|
+
if not task or task.expired or task.inactive:
|
|
205
|
+
break
|
|
206
|
+
|
|
207
|
+
_timer = datetime.datetime.now()
|
|
208
|
+
|
|
209
|
+
triggeredTimer = task.startTimer + task.timer * task.total_repetition_num
|
|
210
|
+
if (lastTimer < triggeredTimer <= _timer or lastTimer > triggeredTimer + task.timer) and task.active:
|
|
211
|
+
task.fn()
|
|
212
|
+
|
|
213
|
+
self._app._managers_['schedules'][task.key] = {
|
|
214
|
+
**self._app._managers_['schedules'][task.key],
|
|
215
|
+
'total_repetition_num': task.total_repetition_num + 1,
|
|
216
|
+
'lastTimer': _timer
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
lastTimer = _timer
|
|
220
|
+
time.sleep(self._app.server.intervalTime)
|
|
221
|
+
|
|
222
|
+
del self._taskHandlers[key]
|
|
168
223
|
|
|
169
224
|
@overload
|
|
170
|
-
def add(self, timer: datetime.timedelta, fn: Callable, *, key: str | None = None, startTimer: datetime.datetime | None = None, expected_repetition_num: int = 0, auto_remove: bool = False):
|
|
225
|
+
def add(self, timer: datetime.timedelta, fn: Callable, *, key: str | None = None, startTimer: datetime.datetime | None = None, expected_repetition_num: int = 0, auto_remove: bool = False, mode: Literal['multiprocessing', 'threading', 'asyncio'] = 'multiprocessing'):
|
|
171
226
|
'''
|
|
172
227
|
通过函数添加一个任务。
|
|
173
228
|
|
|
@@ -193,10 +248,12 @@ class Scheduler:
|
|
|
193
248
|
- expected_repetition_num: 期望的重复次数,0代表无限重复。
|
|
194
249
|
|
|
195
250
|
- auto_remove: 若`expected_repetition_num > 0`且当前计划过期,是否自动删除该计划。
|
|
251
|
+
|
|
252
|
+
- mode: 运行的模式是进程、线程还是协程。
|
|
196
253
|
'''
|
|
197
254
|
|
|
198
255
|
@overload
|
|
199
|
-
def add(self, timer: datetime.timedelta, *, key: str | None = None, startTimer: datetime.datetime | None = None, expected_repetition_num: int = 0, auto_remove: bool = False):
|
|
256
|
+
def add(self, timer: datetime.timedelta, *, key: str | None = None, startTimer: datetime.datetime | None = None, expected_repetition_num: int = 0, auto_remove: bool = False, mode: Literal['multiprocessing', 'threading', 'asyncio'] = 'multiprocessing'):
|
|
200
257
|
'''
|
|
201
258
|
通过装饰器添加一个任务。
|
|
202
259
|
|
|
@@ -221,13 +278,15 @@ class Scheduler:
|
|
|
221
278
|
- expected_repetition_num: 期望的重复次数,0代表无限重复。
|
|
222
279
|
|
|
223
280
|
- auto_remove: 若`expected_repetition_num > 0`且当前计划过期,是否自动删除该计划。
|
|
281
|
+
|
|
282
|
+
- mode: 运行的模式是进程、线程还是协程。
|
|
224
283
|
'''
|
|
225
284
|
|
|
226
|
-
def add(self, timer: datetime.timedelta, fn: Callable | None = None, *, key: str | None = None, startTimer: datetime.datetime | None = None, expected_repetition_num: int = 0, auto_remove: bool = False):
|
|
227
|
-
if
|
|
285
|
+
def add(self, timer: datetime.timedelta, fn: Callable | None = None, *, key: str | None = None, startTimer: datetime.datetime | None = None, expected_repetition_num: int = 0, auto_remove: bool = False, mode: Literal['multiprocessing', 'threading', 'asyncio'] = 'multiprocessing'):
|
|
286
|
+
if key is None:
|
|
228
287
|
key = str(uuid.uuid4())
|
|
229
288
|
|
|
230
|
-
if
|
|
289
|
+
if startTimer is None:
|
|
231
290
|
startTimer = datetime.datetime.now()
|
|
232
291
|
|
|
233
292
|
if fn:
|
|
@@ -238,7 +297,9 @@ class Scheduler:
|
|
|
238
297
|
'expected_repetition_num': expected_repetition_num,
|
|
239
298
|
'total_repetition_num': 0,
|
|
240
299
|
'auto_remove': auto_remove,
|
|
241
|
-
'active': True
|
|
300
|
+
'active': True,
|
|
301
|
+
'mode': mode,
|
|
302
|
+
'lastTimer': None
|
|
242
303
|
}
|
|
243
304
|
return
|
|
244
305
|
|
|
@@ -250,7 +311,9 @@ class Scheduler:
|
|
|
250
311
|
'expected_repetition_num': expected_repetition_num,
|
|
251
312
|
'total_repetition_num': 0,
|
|
252
313
|
'auto_remove': auto_remove,
|
|
253
|
-
'active': True
|
|
314
|
+
'active': True,
|
|
315
|
+
'mode': mode,
|
|
316
|
+
'lastTimer': None
|
|
254
317
|
}
|
|
255
318
|
return fn
|
|
256
319
|
return wrapper
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: CheeseAPI
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.0
|
|
4
4
|
Summary: 一款web协程框架。
|
|
5
5
|
Project-URL: Source, https://github.com/CheeseUnknown/CheeseAPI
|
|
6
6
|
Author-email: Cheese Unknown <cheese@cheese.ren>
|
|
7
7
|
License-File: LICENSE
|
|
8
8
|
Keywords: API,BackEnd
|
|
9
|
-
Requires-Dist: cheeselog==1.
|
|
9
|
+
Requires-Dist: cheeselog==1.1.*
|
|
10
10
|
Requires-Dist: cheesesignal==1.1.*
|
|
11
11
|
Requires-Dist: dill
|
|
12
12
|
Requires-Dist: email-validator
|
|
13
13
|
Requires-Dist: httptools
|
|
14
14
|
Requires-Dist: pydantic
|
|
15
|
+
Requires-Dist: setproctitle
|
|
15
16
|
Requires-Dist: uvloop
|
|
16
17
|
Requires-Dist: websockets
|
|
17
18
|
Requires-Dist: xmltodict
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "CheeseAPI"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.4.0"
|
|
8
8
|
description = "一款web协程框架。"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license-files = { paths = [ "LICENSE" ] }
|
|
@@ -14,7 +14,7 @@ authors = [
|
|
|
14
14
|
keywords = [ 'API', 'BackEnd' ]
|
|
15
15
|
|
|
16
16
|
dependencies = [
|
|
17
|
-
"CheeseLog==1.
|
|
17
|
+
"CheeseLog==1.1.*",
|
|
18
18
|
"xmltodict",
|
|
19
19
|
"websockets",
|
|
20
20
|
"uvloop",
|
|
@@ -22,7 +22,8 @@ dependencies = [
|
|
|
22
22
|
"CheeseSignal==1.1.*",
|
|
23
23
|
"dill",
|
|
24
24
|
"pydantic",
|
|
25
|
-
"email-validator"
|
|
25
|
+
"email-validator",
|
|
26
|
+
"setproctitle"
|
|
26
27
|
]
|
|
27
28
|
|
|
28
29
|
[project.urls]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|