CheeseAPI 2.0.7b5__tar.gz → 2.0.8b3__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.
@@ -6,4 +6,4 @@ from CheeseAPI.websocket import Websocket
6
6
  from CheeseAPI.file import File
7
7
  from CheeseAPI.route import Route, RouteProxy
8
8
  from CheeseAPI.validator import validator
9
-
9
+ from CheeseAPI.scheduler import Task
@@ -334,7 +334,7 @@ class AppProxy:
334
334
  async def get_response(self, request: Request) -> Response:
335
335
  if inspect.isfunction(request.fn):
336
336
  try:
337
- signature = inspect.signature(request.fn)
337
+ signature = inspect.signature(request.fn, follow_wrapped = False)
338
338
  if 'request' in signature.parameters or any(p.kind == inspect.Parameter.VAR_KEYWORD for p in signature.parameters.values()):
339
339
  return await request.fn(request = request)
340
340
  else:
@@ -473,9 +473,9 @@ class AppProxy:
473
473
  static.websocket_data_decode = app.sync_server_data_decode
474
474
  static.websocket_data_encode = app.sync_server_data_encode
475
475
  if app.sync_server_url:
476
- static.websocket_sync_servers = (redis.ConnectionPool.from_url(app.sync_server_url), redis.asyncio.ConnectionPool.from_url(app.sync_server_url))
476
+ static.websocket_sync_servers = (redis.ConnectionPool.from_url(app.sync_server_url, socket_timeout = app.sync_server_timeout, socket_connect_timeout = app.sync_server_timeout), redis.asyncio.ConnectionPool.from_url(app.sync_server_url, socket_timeout = app.sync_server_timeout, socket_connect_timeout = app.sync_server_timeout))
477
477
  static.websocket_sync_server = {}
478
- app.scheduler._proxy.init(app)
478
+ static.scheduler_sync_servers = (redis.ConnectionPool.from_url(app.sync_server_url, socket_timeout = app.sync_server_timeout, socket_connect_timeout = app.sync_server_timeout), redis.asyncio.ConnectionPool.from_url(app.sync_server_url, socket_timeout = app.sync_server_timeout, socket_connect_timeout = app.sync_server_timeout))
479
479
 
480
480
  class CheeseAPI:
481
481
  __slots__ = ('_host', '_port', '_ipv6', '_logger_path', '_dual_stack', '_socket_backlog', '_socket_send_buffer_size', '_socket_receive_buffer_size', '_workers', '_ssl_cert', '_ssl_key', '_sync_server_url', '_static_path', '_printer', '_compress', '_compress_min_length', '_compress_level', '_manual_modules', '_exclude_modules', '_priority_modules', '_sync_server_data_encode', '_sync_server_data_decode', '_logger_messages', '_logger', '_is_running', '_request_timeout', '_keep_alive', '_keep_alive_timeout', '_keep_alive_max_requests', '_AppProxy_Class', '_RequestProxy_Class', '_proxy', '_signal', '_ResponseProxy_Class', '_RouteProxy_Class', '_route', '_WebsocketProxy_Class', '_cors', '_SchedulerProxy_Class', '_scheduler', '_sync_server_timeout')
@@ -150,7 +150,7 @@ class AppRoute(Route):
150
150
  self._routes: dict[str, dict[HTTP_METHOD_TYPE, RouteDict]] = {}
151
151
  self._patterns: list[Pattern] = [
152
152
  {
153
- 'pattern': re.compile(r'-?(0|[1-9]\d*)'),
153
+ 'pattern': re.compile(r'-?(?:0|[1-9]\d*)'),
154
154
  'weight': 5,
155
155
  'type': int,
156
156
  'key': 'int'
@@ -1,4 +1,4 @@
1
- import inspect
1
+ import inspect, os
2
2
  import datetime, uuid, threading, multiprocessing, asyncio, time, json
3
3
  from typing import Callable, Literal, TYPE_CHECKING
4
4
 
@@ -228,12 +228,13 @@ class Scheduler:
228
228
  return self._proxy.get_tasks()
229
229
 
230
230
  class SchedulerProxy:
231
- __slots__ = ('app', '_tasks')
231
+ __slots__ = ('app', '_tasks', '_pubsub_ready')
232
232
 
233
233
  def __init__(self, app: 'CheeseAPI'):
234
234
  self.app: 'CheeseAPI' = app
235
235
 
236
236
  self._tasks: dict[str, Task] = {}
237
+ self._pubsub_ready: bool = False
237
238
 
238
239
  def __getstate__(self):
239
240
  return None, {
@@ -243,25 +244,13 @@ class SchedulerProxy:
243
244
  def __setstate__(self, state):
244
245
  self.app = state[1]['app']
245
246
  self._tasks = {}
246
-
247
- def init(self, app = None):
248
- if not app:
249
- app = self.app
250
-
251
- if app.sync_server_url:
252
- static.scheduler_sync_servers = (redis.ConnectionPool.from_url(app.sync_server_url), redis.asyncio.ConnectionPool.from_url(app.sync_server_url))
253
- threading.Thread(target = self._start_pubsub, args = (app,), daemon = True).start()
254
- coro = self._async_start_pubsub(app)
255
- try:
256
- asyncio.create_task(coro)
257
- except RuntimeError:
258
- coro.close()
247
+ self._pubsub_ready = False
259
248
 
260
249
  def _start_pubsub(self, app: 'CheeseAPI'):
261
250
  try:
262
251
  while True:
263
252
  try:
264
- pubsub = redis.Redis(connection_pool = static.scheduler_sync_servers[0]).pubsub()
253
+ pubsub = redis.from_url(app.sync_server_url, socket_timeout = None, socket_connect_timeout = None).pubsub()
265
254
  pubsub.subscribe('CheeseAPI_scheduler')
266
255
  for message in pubsub.listen():
267
256
  if message['type'] == 'message':
@@ -278,16 +267,17 @@ class SchedulerProxy:
278
267
  elif data[0] == 'remove':
279
268
  self.remove(data[1])
280
269
  except redis.exceptions.RedisError:
270
+ pubsub.close()
281
271
  time.sleep(app.sync_server_timeout)
282
272
  except (KeyboardInterrupt, SystemExit):
283
273
  ...
284
274
 
285
275
  async def _async_start_pubsub(self, app: 'CheeseAPI'):
286
- pubsub = redis.asyncio.Redis(connection_pool = static.scheduler_sync_servers[1]).pubsub()
287
- await pubsub.subscribe('CheeseAPI_scheduler')
288
276
  try:
289
277
  while True:
290
278
  try:
279
+ pubsub = redis.asyncio.from_url(app.sync_server_url, socket_timeout = None, socket_connect_timeout = None).pubsub()
280
+ await pubsub.subscribe('CheeseAPI_scheduler')
291
281
  async for message in pubsub.listen():
292
282
  if message['type'] == 'message':
293
283
  data = json.loads(message['data'])
@@ -305,12 +295,20 @@ class SchedulerProxy:
305
295
  except redis.exceptions.RedisError:
306
296
  await pubsub.close()
307
297
  await asyncio.sleep(app.sync_server_timeout)
308
- await self._async_start_pubsub(app)
309
298
  except (KeyboardInterrupt, SystemExit):
310
299
  ...
311
300
 
312
301
  def add(self, interval_time: float, fn: Callable | None = None, *, first_run_timer: datetime.datetime | None = None, expected_run_num: int | None = None, key: str | None = None, run_type: Literal['THREAD', 'PROCESS'] = 'THREAD', args: tuple = (), kwargs: dict = {}, auto_remove: bool = False, timeout: float | None = None) -> Callable | Task:
313
302
  if fn:
303
+ if self.app.sync_server_url and not self._pubsub_ready:
304
+ self._pubsub_ready = True
305
+ threading.Thread(target = self._start_pubsub, args = (self.app,), daemon = True).start()
306
+ coro = self._async_start_pubsub(self.app)
307
+ try:
308
+ asyncio.create_task(coro)
309
+ except RuntimeError:
310
+ coro.close()
311
+
314
312
  task = Task(fn, interval_time, first_run_timer = first_run_timer, expected_run_num = expected_run_num, key = key, run_type = run_type, args = args, kwargs = kwargs, auto_remove = auto_remove, timeout = timeout, _scheduler_proxy = self)
315
313
  task._queue.put(None)
316
314
 
@@ -331,6 +329,15 @@ class SchedulerProxy:
331
329
 
332
330
  async def async_add(self, interval_time: float | None = None, fn: Callable | None = None, *, first_run_timer: datetime.datetime | None = None, expected_run_num: int | None = None, key: str | None = None, args: tuple = (), kwargs: dict = {}, auto_remove: bool = False, timeout: float | None = None) -> Callable | Task:
333
331
  if fn is not None:
332
+ if self.app.sync_server_url and not self._pubsub_ready:
333
+ self._pubsub_ready = True
334
+ threading.Thread(target = self._start_pubsub, args = (self.app,), daemon = True).start()
335
+ coro = self._async_start_pubsub(self.app)
336
+ try:
337
+ asyncio.create_task(coro)
338
+ except RuntimeError:
339
+ coro.close()
340
+
334
341
  task = Task(fn, interval_time = interval_time, first_run_timer = first_run_timer, expected_run_num = expected_run_num, key = key, run_type = 'ASYNC', args = args, kwargs = kwargs, auto_remove = auto_remove, timeout = timeout, _scheduler_proxy = self)
335
342
  task._queue.put(None)
336
343
 
@@ -349,13 +356,12 @@ class SchedulerProxy:
349
356
  return _fn
350
357
  return wrapper
351
358
 
352
- def task_processing(self, key: str, queue: multiprocessing.Queue, fn, *args, **kwargs):
359
+ def task_processing(self, task: Task, queue: multiprocessing.Queue, fn, *args, **kwargs):
353
360
  try:
354
361
  queue.get()
355
362
 
356
- task = self.get_task(key)
357
363
  if static.scheduler_sync_servers:
358
- task._queue.get()
364
+ self.get_task(task.key)._queue.get()
359
365
  if task.first_run_timer:
360
366
  time.sleep(max(0, task.first_run_timer.timestamp() - time.time()))
361
367
 
@@ -372,21 +378,26 @@ class SchedulerProxy:
372
378
  except Exception as e:
373
379
  self.app.printer.scheduler_error(e, task)
374
380
 
381
+ if queue.qsize():
382
+ break
383
+
375
384
  task._last_run_time = time.time() - now
376
385
  task._last_run_timer = datetime.datetime.fromtimestamp(now)
377
386
  task._run_num += 1
378
387
 
379
388
  if static.scheduler_sync_servers:
380
389
  sync_server = redis.Redis(connection_pool = static.scheduler_sync_servers[0])
381
- sync_server.hset('CheeseAPI_scheduler_tasks', key, json.dumps(task._to_dict()))
382
- sync_server.hpexpire('CheeseAPI_scheduler_tasks', int(task.timeout * 1000), key)
390
+ sync_server.hset('CheeseAPI_scheduler_tasks', task.key, json.dumps(task._to_dict()))
391
+ sync_server.hpexpire('CheeseAPI_scheduler_tasks', int(task.timeout * 1000), task.key)
383
392
 
384
393
  time.sleep(max(0, task.interval_time - time.time() + now))
385
394
  except (KeyboardInterrupt, SystemExit):
386
395
  ...
387
396
 
388
- if static.scheduler_sync_servers and task:
389
- redis.Redis(connection_pool = static.scheduler_sync_servers[0]).hpersist('CheeseAPI_scheduler_tasks', key)
397
+ if static.scheduler_sync_servers:
398
+ _redis = redis.Redis(connection_pool = static.scheduler_sync_servers[0])
399
+ if _redis.hexists('CheeseAPI_scheduler_tasks', task.key):
400
+ _redis.hpersist('CheeseAPI_scheduler_tasks', task.key)
390
401
 
391
402
  async def async_task_processing(self, key: str, queue: multiprocessing.Queue, fn, *args, **kwargs):
392
403
  queue.get()
@@ -410,6 +421,9 @@ class SchedulerProxy:
410
421
  except Exception as e:
411
422
  self.app.printer.scheduler_error(e, task)
412
423
 
424
+ if queue.qsize():
425
+ break
426
+
413
427
  task._last_run_time = time.time() - now
414
428
  task._last_run_timer = datetime.datetime.fromtimestamp(now)
415
429
  task._run_num += 1
@@ -429,8 +443,10 @@ class SchedulerProxy:
429
443
  if static.scheduler_sync_servers is not None:
430
444
  await redis.asyncio.Redis(connection_pool = static.scheduler_sync_servers[1]).hdel('CheeseAPI_scheduler_tasks', key)
431
445
  else:
432
- if static.scheduler_sync_servers is not None:
433
- await redis.asyncio.Redis(connection_pool = static.scheduler_sync_servers[1]).hpersist('CheeseAPI_scheduler_tasks', key)
446
+ if static.scheduler_sync_servers:
447
+ _redis = redis.asyncio.Redis(connection_pool = static.scheduler_sync_servers[1])
448
+ if await _redis.hexists('CheeseAPI_scheduler_tasks', key):
449
+ await _redis.hpersist('CheeseAPI_scheduler_tasks', key)
434
450
 
435
451
  def join(self, task: Task):
436
452
  if task.run_type == 'THREAD' and isinstance(task._handler, threading.Thread):
@@ -454,9 +470,9 @@ class SchedulerProxy:
454
470
  return
455
471
 
456
472
  if task.run_type == 'THREAD':
457
- task._handler = threading.Thread(target = self.task_processing, args = (key, task._queue, task.fn, *task.args), kwargs = task.kwargs, daemon = True)
473
+ task._handler = threading.Thread(target = self.task_processing, args = (task, task._queue, task.fn, *task.args), kwargs = task.kwargs, daemon = True)
458
474
  elif task.run_type == 'PROCESS':
459
- task._handler = multiprocessing.get_context('spawn').Process(target = self.task_processing, args = (key, task._queue, task.fn, *task.args), kwargs = task.kwargs, daemon = True)
475
+ task._handler = multiprocessing.get_context('spawn').Process(target = self.task_processing, args = (task, task._queue, task.fn, *task.args), kwargs = task.kwargs, daemon = True)
460
476
  task._handler.start()
461
477
 
462
478
  if task.auto_remove:
@@ -482,29 +498,31 @@ class SchedulerProxy:
482
498
  if not task.is_running:
483
499
  raise KeyError(f'Task with key "{key}" is not running')
484
500
 
485
- _task = self._tasks.get(key)
486
- if not _task:
501
+ local_task = self._tasks.get(key)
502
+ if not local_task:
487
503
  if static.scheduler_sync_servers:
488
504
  redis.Redis(connection_pool = static.scheduler_sync_servers[0]).publish('CheeseAPI_scheduler', json.dumps(['stop', key]))
489
505
  else:
490
- _task._queue.put(None)
491
- if static.scheduler_sync_servers:
492
- redis.Redis(connection_pool = static.scheduler_sync_servers[0]).hset('CheeseAPI_scheduler_tasks', key, json.dumps(task._to_dict()))
506
+ local_task._queue.put(None)
507
+ if static.scheduler_sync_servers:
508
+ redis.Redis(connection_pool = static.scheduler_sync_servers[0]).hset('CheeseAPI_scheduler_tasks', key, json.dumps(task._to_dict()))
509
+
510
+ time.sleep(self.app.sync_server_timeout)
493
511
 
494
512
  def remove(self, key: str):
495
513
  task = self.get_task(key)
496
514
  if not task:
497
515
  return
498
516
 
499
- _task = self._tasks.get(key)
500
- if not _task:
517
+ local_task = self._tasks.get(key)
518
+ if not local_task:
501
519
  if static.scheduler_sync_servers:
502
520
  redis.Redis(connection_pool = static.scheduler_sync_servers[0]).publish('CheeseAPI_scheduler', json.dumps(['remove', key]))
503
521
  else:
504
- _task._queue.put(None)
522
+ local_task._queue.put(None)
505
523
  self._tasks.pop(key, None)
506
- if static.scheduler_sync_servers:
507
- redis.Redis(connection_pool = static.scheduler_sync_servers[0]).hdel('CheeseAPI_scheduler_tasks', key)
524
+ if static.scheduler_sync_servers:
525
+ redis.Redis(connection_pool = static.scheduler_sync_servers[0]).hdel('CheeseAPI_scheduler_tasks', key)
508
526
 
509
527
  async def async_stop(self, key: str):
510
528
  task = await self.async_get_task(key)
@@ -513,29 +531,29 @@ class SchedulerProxy:
513
531
  if not task.is_running:
514
532
  raise KeyError(f'Task with key "{key}" is not running')
515
533
 
516
- _task = self._tasks.get(key)
517
- if not _task:
534
+ local_task = self._tasks.get(key)
535
+ if not local_task:
518
536
  if static.scheduler_sync_servers:
519
537
  await redis.asyncio.Redis(connection_pool = static.scheduler_sync_servers[1]).publish('CheeseAPI_scheduler', json.dumps(['stop', key]))
520
538
  else:
521
- _task._queue.put(None)
522
- if static.scheduler_sync_servers:
523
- await redis.asyncio.Redis(connection_pool = static.scheduler_sync_servers[1]).hset('CheeseAPI_scheduler_tasks', key, json.dumps(task._to_dict()))
539
+ local_task._queue.put(None)
540
+ if static.scheduler_sync_servers:
541
+ await redis.asyncio.Redis(connection_pool = static.scheduler_sync_servers[1]).hset('CheeseAPI_scheduler_tasks', key, json.dumps(task._to_dict()))
524
542
 
525
543
  async def async_remove(self, key: str):
526
544
  task = await self.async_get_task(key)
527
545
  if not task:
528
546
  return
529
547
 
530
- _task = self._tasks.get(key)
531
- if not _task:
548
+ local_task = self._tasks.get(key)
549
+ if not local_task:
532
550
  if static.scheduler_sync_servers:
533
551
  await redis.asyncio.Redis(connection_pool = static.scheduler_sync_servers[1]).publish('CheeseAPI_scheduler', json.dumps(['remove', key]))
534
552
  else:
535
- _task._queue.put(None)
553
+ local_task._queue.put(None)
536
554
  self._tasks.pop(key, None)
537
- if static.scheduler_sync_servers:
538
- await redis.asyncio.Redis(connection_pool = static.scheduler_sync_servers[1]).hdel('CheeseAPI_scheduler_tasks', key)
555
+ if static.scheduler_sync_servers:
556
+ await redis.asyncio.Redis(connection_pool = static.scheduler_sync_servers[1]).hdel('CheeseAPI_scheduler_tasks', key)
539
557
 
540
558
  def get_task(self, key: str) -> Task | None:
541
559
  if static.scheduler_sync_servers is not None:
@@ -566,7 +584,7 @@ class SchedulerProxy:
566
584
  async def async_get_tasks(self) -> dict[str, Task]:
567
585
  if static.scheduler_sync_servers is not None:
568
586
  return {
569
- key: Task.from_dict(json.loads(data), self) for key, data in (await redis.asyncio.Redis(connection_pool = static.scheduler_sync_servers[1]).hgetall('CheeseAPI_scheduler_tasks')).items()
587
+ key.decode(): Task.from_dict(json.loads(data), self) for key, data in (await redis.asyncio.Redis(connection_pool = static.scheduler_sync_servers[1]).hgetall('CheeseAPI_scheduler_tasks')).items()
570
588
  }
571
589
 
572
590
  return self._tasks
@@ -57,7 +57,7 @@ def validator(*, json_model: pydantic.BaseModel | None = None, form_model: pydan
57
57
  else:
58
58
  return Response(json.loads(e.json()), 400)
59
59
 
60
- signature = inspect.signature(fn)
60
+ signature = inspect.signature(fn, follow_wrapped = False)
61
61
  if 'request' in signature.parameters or any(p.kind == inspect.Parameter.VAR_KEYWORD for p in signature.parameters.values()):
62
62
  return await fn(*args, request = request, **kwargs)
63
63
  else:
@@ -2,7 +2,7 @@ import base64, hashlib, asyncio, ssl, struct, json
2
2
  from functools import partial
3
3
  from typing import TYPE_CHECKING, AsyncIterable, Self
4
4
 
5
- import redis
5
+ import redis, redis.exceptions
6
6
 
7
7
  from CheeseAPI import static
8
8
  from CheeseAPI.response import Response
@@ -230,7 +230,7 @@ class WebsocketProxy:
230
230
  async def connect(self) -> AsyncIterable[Response]:
231
231
  Websocket.connectors.setdefault(self.websocket.request.path, []).append(self.websocket)
232
232
  if self.app.sync_server_url and self.websocket.request.path not in static.websocket_sync_server:
233
- static.websocket_sync_server[self.websocket.request.path] = redis.asyncio.Redis.from_url(self.app.sync_server_url)
233
+ static.websocket_sync_server[self.websocket.request.path] = redis.asyncio.Redis.from_url(self.app.sync_server_url, socket_timeout = self.app.sync_server_timeout, socket_connect_timeout = self.app.sync_server_timeout)
234
234
  asyncio.create_task(self.sync_server_running())
235
235
 
236
236
  loop = asyncio.get_running_loop()
@@ -248,9 +248,9 @@ class WebsocketProxy:
248
248
  async def sync_server_running(self):
249
249
  try:
250
250
  if self.app.sync_server_url.startswith('redis'):
251
- while self.websocket.is_running:
251
+ while True:
252
252
  try:
253
- pubsub = static.websocket_sync_server[self.websocket.request.path].pubsub()
253
+ pubsub = redis.asyncio.from_url(self.app.sync_server_url, socket_timeout = None, socket_connect_timeout = None).pubsub()
254
254
  await pubsub.subscribe(self.websocket.request.path)
255
255
  async for message in pubsub.listen():
256
256
  if message['type'] != 'message':
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: CheeseAPI
3
- Version: 2.0.7b5
3
+ Version: 2.0.8b3
4
4
  Summary: 一款web协程框架
5
5
  Project-URL: Source, https://github.com/CheeseUnknown/CheeseAPI
6
6
  Author-email: Cheese Unknown <cheese@cheese.ren>
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "CheeseAPI"
7
- version = "2.0.7-beta.5"
7
+ version = "2.0.8-beta.3"
8
8
  description = "一款web协程框架"
9
9
  readme = "README.md"
10
10
  license-files = { paths = [ "LICENSE" ] }
File without changes
File without changes
File without changes