feat: support run job apscheduler
This commit is contained in:
parent
293f3ba937
commit
05021865c7
@ -7,6 +7,7 @@ requires-python = ">=3.11"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"aiofiles>=24.1.0",
|
"aiofiles>=24.1.0",
|
||||||
"alembic>=1.13.3",
|
"alembic>=1.13.3",
|
||||||
|
"apscheduler>=3.10.4",
|
||||||
"asyncmy>=0.2.9",
|
"asyncmy>=0.2.9",
|
||||||
"black>=24.10.0",
|
"black>=24.10.0",
|
||||||
"fakeredis>=2.26.1",
|
"fakeredis>=2.26.1",
|
||||||
|
13
src/core/scheduler.py
Normal file
13
src/core/scheduler.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
||||||
|
from persica.factory.component import AsyncInitializingComponent
|
||||||
|
|
||||||
|
|
||||||
|
class TimeScheduler(AsyncInitializingComponent):
|
||||||
|
def __init__(self):
|
||||||
|
self.scheduler = AsyncIOScheduler(timezone="Asia/ShangHai")
|
||||||
|
|
||||||
|
async def initialize(self):
|
||||||
|
self.scheduler.start()
|
||||||
|
|
||||||
|
async def shutdown(self):
|
||||||
|
self.scheduler.shutdown()
|
@ -1 +1,2 @@
|
|||||||
from ._handler import handler
|
from ._handler import handler
|
||||||
|
from ._job import job
|
||||||
|
86
src/plugin/_job.py
Normal file
86
src/plugin/_job.py
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Any, ParamSpec, TypeVar, Callable
|
||||||
|
|
||||||
|
P = ParamSpec("P")
|
||||||
|
R = TypeVar("R")
|
||||||
|
|
||||||
|
_JOB_ATTR_NAME = "_job_data"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(init=True)
|
||||||
|
class JobData:
|
||||||
|
trigger: str
|
||||||
|
name: str
|
||||||
|
kwargs: Any = None
|
||||||
|
|
||||||
|
|
||||||
|
class _Job:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
trigger: str,
|
||||||
|
name: str,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
|
self.trigger = trigger
|
||||||
|
self.name = name
|
||||||
|
self.kwargs = kwargs
|
||||||
|
|
||||||
|
def __call__(self, func: Callable[P, R]) -> Callable[P, R]:
|
||||||
|
data = JobData(
|
||||||
|
trigger=self.trigger,
|
||||||
|
name=self.name,
|
||||||
|
kwargs=self.kwargs,
|
||||||
|
)
|
||||||
|
if hasattr(func, _JOB_ATTR_NAME):
|
||||||
|
job_datas = getattr(func, _JOB_ATTR_NAME)
|
||||||
|
job_datas.append(data)
|
||||||
|
setattr(func, _JOB_ATTR_NAME, job_datas)
|
||||||
|
else:
|
||||||
|
setattr(func, _JOB_ATTR_NAME, [data])
|
||||||
|
return func
|
||||||
|
|
||||||
|
|
||||||
|
class _RunCron(_Job):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
|
super().__init__(
|
||||||
|
trigger="cron",
|
||||||
|
name=name,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class _RunInterval(_Job):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
|
super().__init__(
|
||||||
|
trigger="interval",
|
||||||
|
name=name,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class _RunDate(_Job):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
|
super().__init__(
|
||||||
|
trigger="date",
|
||||||
|
name=name,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# noinspection PyPep8Naming
|
||||||
|
class job:
|
||||||
|
cron = _RunCron
|
||||||
|
interval = _RunInterval
|
||||||
|
date = _RunDate
|
@ -3,11 +3,14 @@ from typing import List, ClassVar, TYPE_CHECKING, Optional
|
|||||||
from types import MethodType
|
from types import MethodType
|
||||||
from multiprocessing import RLock as Lock
|
from multiprocessing import RLock as Lock
|
||||||
|
|
||||||
|
from apscheduler.job import Job
|
||||||
from persica.factory.component import AsyncInitializingComponent
|
from persica.factory.component import AsyncInitializingComponent
|
||||||
|
|
||||||
from src.app import app
|
from src.app import app
|
||||||
|
from src.core.scheduler import TimeScheduler
|
||||||
from src.core.web_app import WebApp
|
from src.core.web_app import WebApp
|
||||||
from src.plugin._handler import HandlerData
|
from src.plugin._handler import HandlerData
|
||||||
|
from src.plugin._job import JobData, _JOB_ATTR_NAME
|
||||||
from src.services.users.services import UserServices
|
from src.services.users.services import UserServices
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@ -28,6 +31,7 @@ class Plugin(AsyncInitializingComponent):
|
|||||||
|
|
||||||
_prefix: Optional[str] = None
|
_prefix: Optional[str] = None
|
||||||
_handlers: Optional[List[HandlerData]] = None
|
_handlers: Optional[List[HandlerData]] = None
|
||||||
|
_jobs: Optional[List[Job]] = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def handlers(self) -> List[HandlerData]:
|
def handlers(self) -> List[HandlerData]:
|
||||||
@ -48,6 +52,31 @@ class Plugin(AsyncInitializingComponent):
|
|||||||
self._handlers.append(data)
|
self._handlers.append(data)
|
||||||
return self._handlers
|
return self._handlers
|
||||||
|
|
||||||
|
def _install_jobs(self) -> None:
|
||||||
|
if self._jobs is None:
|
||||||
|
self._jobs = []
|
||||||
|
sche: TimeScheduler = app.factory.get_object(TimeScheduler)
|
||||||
|
for attr in dir(self):
|
||||||
|
# noinspection PyUnboundLocalVariable
|
||||||
|
if (
|
||||||
|
not (attr.startswith("_") or attr in _EXCLUDE_ATTRS)
|
||||||
|
and isinstance(func := getattr(self, attr), MethodType)
|
||||||
|
and (datas := getattr(func, _JOB_ATTR_NAME, []))
|
||||||
|
):
|
||||||
|
for data in datas:
|
||||||
|
data: "JobData"
|
||||||
|
self._jobs.append(
|
||||||
|
sche.scheduler.add_job(func, data.trigger, **data.kwargs)
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def jobs(self) -> List[Job]:
|
||||||
|
with self._lock:
|
||||||
|
if self._jobs is None:
|
||||||
|
self._jobs = []
|
||||||
|
self._install_jobs()
|
||||||
|
return self._jobs
|
||||||
|
|
||||||
async def initialize(self):
|
async def initialize(self):
|
||||||
await self.install()
|
await self.install()
|
||||||
|
|
||||||
@ -59,7 +88,7 @@ class Plugin(AsyncInitializingComponent):
|
|||||||
web: WebApp = app.factory.get_object(WebApp)
|
web: WebApp = app.factory.get_object(WebApp)
|
||||||
auth: UserServices = app.factory.get_object(UserServices)
|
auth: UserServices = app.factory.get_object(UserServices)
|
||||||
|
|
||||||
# self._install_jobs()
|
self._install_jobs()
|
||||||
|
|
||||||
for h in self.handlers:
|
for h in self.handlers:
|
||||||
dep = h.kwargs.get("dependencies", [])
|
dep = h.kwargs.get("dependencies", [])
|
||||||
|
57
uv.lock
57
uv.lock
@ -50,6 +50,20 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/e4/f5/f2b75d2fc6f1a260f340f0e7c6a060f4dd2961cc16884ed851b0d18da06a/anyio-4.6.2.post1-py3-none-any.whl", hash = "sha256:6d170c36fba3bdd840c73d3868c1e777e33676a69c3a72cf0a0d5d6d8009b61d", size = 90377 },
|
{ url = "https://files.pythonhosted.org/packages/e4/f5/f2b75d2fc6f1a260f340f0e7c6a060f4dd2961cc16884ed851b0d18da06a/anyio-4.6.2.post1-py3-none-any.whl", hash = "sha256:6d170c36fba3bdd840c73d3868c1e777e33676a69c3a72cf0a0d5d6d8009b61d", size = 90377 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "apscheduler"
|
||||||
|
version = "3.10.4"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "pytz" },
|
||||||
|
{ name = "six" },
|
||||||
|
{ name = "tzlocal" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/5e/34/5dcb368cf89f93132d9a31bd3747962a9dc874480e54333b0c09fa7d56ac/APScheduler-3.10.4.tar.gz", hash = "sha256:e6df071b27d9be898e486bc7940a7be50b4af2e9da7c08f0744a96d4bd4cef4a", size = 100832 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/13/b5/7af0cb920a476dccd612fbc9a21a3745fb29b1fcd74636078db8f7ba294c/APScheduler-3.10.4-py3-none-any.whl", hash = "sha256:fb91e8a768632a4756a585f79ec834e0e27aad5860bac7eaa523d9ccefd87661", size = 59303 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-timeout"
|
name = "async-timeout"
|
||||||
version = "5.0.0"
|
version = "5.0.0"
|
||||||
@ -520,6 +534,15 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/b4/fb/275137a799169392f1fa88fff2be92f16eee38e982720a8aaadefc4a36b2/python_multipart-0.0.17-py3-none-any.whl", hash = "sha256:15dc4f487e0a9476cc1201261188ee0940165cffc94429b6fc565c4d3045cb5d", size = 24453 },
|
{ url = "https://files.pythonhosted.org/packages/b4/fb/275137a799169392f1fa88fff2be92f16eee38e982720a8aaadefc4a36b2/python_multipart-0.0.17-py3-none-any.whl", hash = "sha256:15dc4f487e0a9476cc1201261188ee0940165cffc94429b6fc565c4d3045cb5d", size = 24453 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pytz"
|
||||||
|
version = "2024.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/3a/31/3c70bf7603cc2dca0f19bdc53b4537a797747a58875b552c8c413d963a3f/pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a", size = 319692 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/11/c3/005fcca25ce078d2cc29fd559379817424e94885510568bc1bc53d7d5846/pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725", size = 508002 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redis"
|
name = "redis"
|
||||||
version = "5.2.0"
|
version = "5.2.0"
|
||||||
@ -541,6 +564,15 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/a0/e9/e58082fbb8cecbb6fb4133033c40cc50c248b1a331582be3a0f39138d65b/simpleeval-1.0.3-py3-none-any.whl", hash = "sha256:e3bdbb8c82c26297c9a153902d0fd1858a6c3774bf53ff4f134788c3f2035c38", size = 15762 },
|
{ url = "https://files.pythonhosted.org/packages/a0/e9/e58082fbb8cecbb6fb4133033c40cc50c248b1a331582be3a0f39138d65b/simpleeval-1.0.3-py3-none-any.whl", hash = "sha256:e3bdbb8c82c26297c9a153902d0fd1858a6c3774bf53ff4f134788c3f2035c38", size = 15762 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "six"
|
||||||
|
version = "1.16.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/71/39/171f1c67cd00715f190ba0b100d606d440a28c93c7714febeca8b79af85e/six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", size = 34041 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254", size = 11053 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sniffio"
|
name = "sniffio"
|
||||||
version = "1.3.1"
|
version = "1.3.1"
|
||||||
@ -642,6 +674,27 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 },
|
{ url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tzdata"
|
||||||
|
version = "2024.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/e1/34/943888654477a574a86a98e9896bae89c7aa15078ec29f490fef2f1e5384/tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc", size = 193282 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a6/ab/7e5f53c3b9d14972843a647d8d7a853969a58aecc7559cb3267302c94774/tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd", size = 346586 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tzlocal"
|
||||||
|
version = "5.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "tzdata", marker = "platform_system == 'Windows'" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/04/d3/c19d65ae67636fe63953b20c2e4a8ced4497ea232c43ff8d01db16de8dc0/tzlocal-5.2.tar.gz", hash = "sha256:8d399205578f1a9342816409cc1e46a93ebd5755e39ea2d85334bea911bf0e6e", size = 30201 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/97/3f/c4c51c55ff8487f2e6d0e618dba917e3c3ee2caae6cf0fbb59c9b1876f2e/tzlocal-5.2-py3-none-any.whl", hash = "sha256:49816ef2fe65ea8ac19d19aa7a1ae0551c834303d5014c6d5a62e4cbda8047b8", size = 17859 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uvicorn"
|
name = "uvicorn"
|
||||||
version = "0.32.0"
|
version = "0.32.0"
|
||||||
@ -662,6 +715,7 @@ source = { virtual = "." }
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "aiofiles" },
|
{ name = "aiofiles" },
|
||||||
{ name = "alembic" },
|
{ name = "alembic" },
|
||||||
|
{ name = "apscheduler" },
|
||||||
{ name = "asyncmy" },
|
{ name = "asyncmy" },
|
||||||
{ name = "black" },
|
{ name = "black" },
|
||||||
{ name = "fakeredis" },
|
{ name = "fakeredis" },
|
||||||
@ -679,8 +733,9 @@ dependencies = [
|
|||||||
|
|
||||||
[package.metadata]
|
[package.metadata]
|
||||||
requires-dist = [
|
requires-dist = [
|
||||||
{ name = "aiofiles" },
|
{ name = "aiofiles", specifier = ">=24.1.0" },
|
||||||
{ name = "alembic", specifier = ">=1.13.3" },
|
{ name = "alembic", specifier = ">=1.13.3" },
|
||||||
|
{ name = "apscheduler" },
|
||||||
{ name = "asyncmy", specifier = ">=0.2.9" },
|
{ name = "asyncmy", specifier = ">=0.2.9" },
|
||||||
{ name = "black", specifier = ">=24.10.0" },
|
{ name = "black", specifier = ">=24.10.0" },
|
||||||
{ name = "fakeredis", specifier = ">=2.26.1" },
|
{ name = "fakeredis", specifier = ">=2.26.1" },
|
||||||
|
Loading…
Reference in New Issue
Block a user