Merge pull request #125 from felinae98/scheduler-api

调整调度器 api
This commit is contained in:
felinae98 2022-10-15 00:57:29 +08:00 committed by GitHub
commit 4be632bdfd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 122 additions and 105 deletions

View File

@ -11,8 +11,9 @@ from ..utils.scheduler_config import SchedulerConfig
from .platform import CategoryNotSupport, NewMessage, StatusChange
class ArknightsSchedConf(SchedulerConfig, name="arknights"):
class ArknightsSchedConf(SchedulerConfig):
name = "arknights"
schedule_type = "interval"
schedule_setting = {"seconds": 30}
@ -25,7 +26,7 @@ class Arknights(NewMessage):
enable_tag = False
enabled = True
is_common = False
scheduler_class = "arknights"
scheduler = ArknightsSchedConf
has_target = False
async def get_target_name(self, _: Target) -> str:
@ -97,7 +98,7 @@ class AkVersion(StatusChange):
enable_tag = False
enabled = True
is_common = False
scheduler_class = "arknights"
scheduler = ArknightsSchedConf
has_target = False
async def get_target_name(self, _: Target) -> str:
@ -152,7 +153,7 @@ class MonsterSiren(NewMessage):
enable_tag = False
enabled = True
is_common = False
scheduler_class = "arknights"
scheduler = ArknightsSchedConf
has_target = False
async def get_target_name(self, _: Target) -> str:
@ -203,7 +204,7 @@ class TerraHistoricusComic(NewMessage):
enable_tag = False
enabled = True
is_common = False
scheduler_class = "arknights"
scheduler = ArknightsSchedConf
has_target = False
async def get_target_name(self, _: Target) -> str:

View File

@ -14,8 +14,9 @@ from ..utils.http import http_args
from .platform import CategoryNotSupport, NewMessage, StatusChange
class BilibiliSchedConf(SchedulerConfig, name="bilibili.com"):
class BilibiliSchedConf(SchedulerConfig):
name = "bilibili.com"
schedule_type = "interval"
schedule_setting = {"seconds": 10}
@ -33,9 +34,6 @@ class _BilibiliClient:
self._http_client = httpx.AsyncClient(**http_args)
res = await self._http_client.get("https://www.bilibili.com/")
if res.status_code != 200:
import ipdb
ipdb.set_trace()
logger.warning("unable to refresh temp cookie")
else:
self._client_refresh_time = datetime.now()
@ -64,7 +62,7 @@ class Bilibili(_BilibiliClient, NewMessage):
enable_tag = True
enabled = True
is_common = True
scheduler_class = "bilibili.com"
scheduler = BilibiliSchedConf
name = "B站"
has_target = True
parse_target_promot = "请输入用户主页的链接"
@ -214,7 +212,7 @@ class Bilibililive(_BilibiliClient, StatusChange):
enable_tag = False
enabled = True
is_common = True
scheduler_class = "bilibili.com"
scheduler = BilibiliSchedConf
name = "Bilibili直播"
has_target = True
@ -288,7 +286,7 @@ class BilibiliBangumi(_BilibiliClient, StatusChange):
enable_tag = False
enabled = True
is_common = True
scheduler_class = "bilibili.com"
scheduler = BilibiliSchedConf
name = "Bilibili剧集"
has_target = True
parse_target_promot = "请输入剧集主页"

View File

@ -2,16 +2,10 @@ from typing import Any
from ..post import Post
from ..types import RawPost, Target
from ..utils import SchedulerConfig, http_client
from ..utils import http_client, scheduler
from .platform import NewMessage
class FF14SchedConf(SchedulerConfig, name="ff14"):
schedule_type = "interval"
schedule_setting = {"seconds": 60}
class FF14(NewMessage):
categories = {}
@ -21,6 +15,7 @@ class FF14(NewMessage):
enabled = True
is_common = False
scheduler_class = "ff14"
scheduler = scheduler("interval", {"seconds": 60})
has_target = False
async def get_target_name(self, _: Target) -> str:

View File

@ -7,16 +7,10 @@ from bs4 import BeautifulSoup, NavigableString, Tag
from ..post import Post
from ..types import Category, RawPost, Target
from ..utils import SchedulerConfig
from ..utils import scheduler
from .platform import CategoryNotSupport, NewMessage
class McbbsSchedConf(SchedulerConfig, name="mcbbs"):
schedule_type = "interval"
schedule_setting = {"hours": 1}
def _format_text(rawtext: str, mode: int) -> str:
"""处理BeautifulSoup生成的string中奇怪的回车+连续空格
mode 0:处理标题
@ -45,7 +39,7 @@ class McbbsNews(NewMessage):
name = "MCBBS幻翼块讯"
enabled = True
is_common = False
scheduler_class = "mcbbs"
scheduler = scheduler("interval", {"hours": 1})
has_target = False
async def get_target_name(self, _: Target) -> str:

View File

@ -7,8 +7,9 @@ from ..utils import SchedulerConfig, http_client
from .platform import NewMessage
class NcmSchedConf(SchedulerConfig, name="music.163.com"):
class NcmSchedConf(SchedulerConfig):
name = "music.163.com"
schedule_type = "interval"
schedule_setting = {"minutes": 1}
@ -20,7 +21,7 @@ class NcmArtist(NewMessage):
enable_tag = False
enabled = True
is_common = True
scheduler_class = "music.163.com"
scheduler = NcmSchedConf
name = "网易云-歌手"
has_target = True
parse_target_promot = "请输入歌手主页包含数字ID的链接"

View File

@ -4,6 +4,7 @@ from typing import Any, Optional
from ..post import Post
from ..types import RawPost, Target
from ..utils import http_client
from .ncm_artist import NcmSchedConf
from .platform import NewMessage
@ -14,7 +15,7 @@ class NcmRadio(NewMessage):
enable_tag = False
enabled = True
is_common = False
scheduler_class = "music.163.com"
scheduler = NcmSchedConf
name = "网易云-电台"
has_target = True
parse_target_promot = "请输入主播电台主页包含数字ID的链接"

View File

@ -4,7 +4,7 @@ import time
from abc import ABC, abstractmethod
from collections import defaultdict
from dataclasses import dataclass
from typing import Any, Collection, Literal, Optional
from typing import Any, Collection, Literal, Optional, Type
import httpx
from nonebot.log import logger
@ -12,6 +12,7 @@ from nonebot.log import logger
from ..plugin_config import plugin_config
from ..post import Post
from ..types import Category, RawPost, Tag, Target, User, UserSubInfo
from ..utils.scheduler_config import SchedulerConfig
class CategoryNotSupport(Exception):
@ -39,7 +40,7 @@ class RegistryABCMeta(RegistryMeta, ABC):
class Platform(metaclass=RegistryABCMeta, base=True):
scheduler_class: str
scheduler: Type[SchedulerConfig]
is_common: bool
enabled: bool
name: str
@ -373,7 +374,7 @@ class NoTargetGroup(Platform, abstract=True):
name = self.DUMMY_STR
self.categories = {}
categories_keys = set()
self.scheduler_class = platform_list[0].scheduler_class
self.scheduler = platform_list[0].scheduler
for platform in platform_list:
if platform.has_target:
raise RuntimeError(
@ -392,7 +393,7 @@ class NoTargetGroup(Platform, abstract=True):
)
categories_keys |= platform_category_key_set
self.categories.update(platform.categories)
if platform.scheduler_class != self.scheduler_class:
if platform.scheduler != self.scheduler:
raise RuntimeError(
"Platform scheduler for {} not fit".format(self.platform_name)
)

View File

@ -6,16 +6,10 @@ from bs4 import BeautifulSoup as bs
from ..post import Post
from ..types import RawPost, Target
from ..utils import SchedulerConfig, http_client
from ..utils import http_client, scheduler
from .platform import NewMessage
class RssSchedConf(SchedulerConfig, name="rss"):
schedule_type = "interval"
schedule_setting = {"seconds": 30}
class Rss(NewMessage):
categories = {}
@ -24,7 +18,7 @@ class Rss(NewMessage):
name = "Rss"
enabled = True
is_common = True
scheduler_class = "rss"
scheduler = scheduler("interval", {"seconds": 30})
has_target = True
async def get_target_name(self, target: Target) -> Optional[str]:

View File

@ -1,5 +1,6 @@
import json
import re
from collections.abc import Callable
from datetime import datetime
from typing import Any, Optional
@ -12,7 +13,8 @@ from ..utils import SchedulerConfig, http_client
from .platform import NewMessage
class WeiboSchedConf(SchedulerConfig, name="weibo.com"):
class WeiboSchedConf(SchedulerConfig):
name = "weibo.com"
schedule_type = "interval"
schedule_setting = {"seconds": 3}
@ -30,7 +32,7 @@ class Weibo(NewMessage):
name = "新浪微博"
enabled = True
is_common = True
scheduler_class = "weibo.com"
scheduler = WeiboSchedConf
has_target = True
parse_target_promot = "请输入用户主页包含数字UID的链接"

View File

@ -1,4 +1,4 @@
from nonebot.log import logger
from typing import Type
from ..config import config
from ..config.db_model import Target
@ -7,43 +7,46 @@ from ..types import Target as T_Target
from ..utils import SchedulerConfig
from .scheduler import Scheduler
scheduler_dict: dict[str, Scheduler] = {}
scheduler_dict: dict[Type[SchedulerConfig], Scheduler] = {}
async def init_scheduler():
_schedule_class_dict: dict[str, list[Target]] = {}
_schedule_class_platform_dict: dict[str, list[str]] = {}
_schedule_class_dict: dict[Type[SchedulerConfig], list[Target]] = {}
_schedule_class_platform_dict: dict[Type[SchedulerConfig], list[str]] = {}
for platform in platform_manager.values():
scheduler_class = platform.scheduler_class
scheduler_config = platform.scheduler
if not hasattr(scheduler_config, "name") or not scheduler_config.name:
scheduler_config.name = f"AnonymousScheduleConfig[{platform.platform_name}]"
platform_name = platform.platform_name
targets = await config.get_platform_target(platform_name)
if scheduler_class not in _schedule_class_dict:
_schedule_class_dict[scheduler_class] = targets
if scheduler_config not in _schedule_class_dict:
_schedule_class_dict[scheduler_config] = targets
else:
_schedule_class_dict[scheduler_class].extend(targets)
if scheduler_class not in _schedule_class_platform_dict:
_schedule_class_platform_dict[scheduler_class] = [platform_name]
_schedule_class_dict[scheduler_config].extend(targets)
if scheduler_config not in _schedule_class_platform_dict:
_schedule_class_platform_dict[scheduler_config] = [platform_name]
else:
_schedule_class_platform_dict[scheduler_class].append(platform_name)
for scheduler_class, target_list in _schedule_class_dict.items():
_schedule_class_platform_dict[scheduler_config].append(platform_name)
for scheduler_config, target_list in _schedule_class_dict.items():
schedulable_args = []
for target in target_list:
schedulable_args.append((target.platform_name, T_Target(target.target)))
platform_name_list = _schedule_class_platform_dict[scheduler_class]
scheduler_dict[scheduler_class] = Scheduler(
scheduler_class, schedulable_args, platform_name_list
platform_name_list = _schedule_class_platform_dict[scheduler_config]
scheduler_dict[scheduler_config] = Scheduler(
scheduler_config, schedulable_args, platform_name_list
)
async def handle_insert_new_target(platform_name: str, target: T_Target):
platform = platform_manager[platform_name]
scheduler_obj = scheduler_dict[platform.scheduler_class]
scheduler_obj = scheduler_dict[platform.scheduler]
scheduler_obj.insert_new_schedulable(platform_name, target)
async def handle_delete_target(platform_name: str, target: T_Target):
platform = platform_manager[platform_name]
scheduler_obj = scheduler_dict[platform.scheduler_class]
scheduler_obj = scheduler_dict[platform.scheduler]
scheduler_obj.delete_schedulable(platform_name, target)

View File

@ -1,5 +1,5 @@
from dataclasses import dataclass
from typing import Optional
from typing import Optional, Type
import nonebot
from nonebot.adapters.onebot.v11.bot import Bot
@ -27,16 +27,15 @@ class Scheduler:
def __init__(
self,
name: str,
scheduler_config: Type[SchedulerConfig],
schedulables: list[tuple[str, Target]],
platform_name_list: list[str],
):
conf = SchedulerConfig.registry.get(name)
self.name = name
if not conf:
logger.error(f"scheduler config [{name}] not found, exiting")
raise RuntimeError(f"{name} not found")
self.scheduler_config = conf
self.name = scheduler_config.name
if not scheduler_config:
logger.error(f"scheduler config [{self.name}] not found, exiting")
raise RuntimeError(f"{self.name} not found")
self.scheduler_config = scheduler_config
self.schedulable_list = []
for platform_name, target in schedulables:
self.schedulable_list.append(
@ -47,7 +46,7 @@ class Scheduler:
self.platform_name_list = platform_name_list
self.pre_weight_val = 0 # 轮调度中“本轮”增加权重和的初值
logger.info(
f"register scheduler for {name} with {self.scheduler_config.schedule_type} {self.scheduler_config.schedule_setting}"
f"register scheduler for {self.name} with {self.scheduler_config.schedule_type} {self.scheduler_config.schedule_setting}"
)
aps.add_job(
self.exec_fetch,

View File

@ -10,9 +10,16 @@ from nonebot.plugin import require
from ..plugin_config import plugin_config
from .http import http_client
from .scheduler_config import SchedulerConfig
from .scheduler_config import SchedulerConfig, scheduler
__all__ = ["http_client", "Singleton", "parse_text", "html_to_text", "SchedulerConfig"]
__all__ = [
"http_client",
"Singleton",
"parse_text",
"html_to_text",
"SchedulerConfig",
"scheduler",
]
class Singleton(type):

View File

@ -5,13 +5,20 @@ class SchedulerConfig:
schedule_type: Literal["date", "interval", "cron"]
schedule_setting: dict
registry: dict[str, Type["SchedulerConfig"]] = {}
name: str
def __init_subclass__(cls, *, name, **kwargs):
super().__init_subclass__(**kwargs)
cls.registry[name] = cls
cls.name = name
def __str__(self):
return f"[{self.name}]-{self.name}-{self.schedule_setting}"
def scheduler(
schedule_type: Literal["date", "interval", "cron"], schedule_setting: dict
) -> Type[SchedulerConfig]:
return type(
"AnonymousScheduleConfig",
(SchedulerConfig,),
{
"schedule_type": schedule_type,
"schedule_setting": schedule_setting,
},
)

View File

@ -92,8 +92,9 @@ def mock_platform(app: App):
from nonebot_bison.types import Category, RawPost, Tag, Target
from nonebot_bison.utils import SchedulerConfig
class MockPlatformSchedConf(SchedulerConfig, name="mock"):
class MockPlatformSchedConf(SchedulerConfig):
name = "mock"
schedule_type = "interval"
schedule_setting = {"seconds": 100}
@ -105,7 +106,7 @@ def mock_platform(app: App):
is_common = True
enable_tag = True
has_target = True
scheduler_class = "mock"
scheduler = MockPlatformSchedConf
categories = {
Category(1): "转发",
Category(2): "视频",
@ -150,16 +151,23 @@ def mock_platform(app: App):
@pytest.fixture
def mock_platform_no_target(app: App):
def mock_scheduler_conf(app):
from nonebot_bison.utils import SchedulerConfig
class MockPlatformSchedConf(SchedulerConfig):
name = "mock"
schedule_type = "interval"
schedule_setting = {"seconds": 100}
return MockPlatformSchedConf
@pytest.fixture
def mock_platform_no_target(app: App, mock_scheduler_conf):
from nonebot_bison.platform.platform import CategoryNotSupport, NewMessage
from nonebot_bison.post import Post
from nonebot_bison.types import Category, RawPost, Tag, Target
from nonebot_bison.utils import SchedulerConfig
class MockPlatformSchedConf(SchedulerConfig, name="mock"):
schedule_type = "interval"
schedule_setting = {"seconds": 100}
class MockPlatform(NewMessage):
@ -167,7 +175,7 @@ def mock_platform_no_target(app: App):
name = "Mock Platform"
enabled = True
is_common = True
scheduler_class = "mock"
scheduler = mock_scheduler_conf
enable_tag = True
has_target = False
categories = {Category(1): "转发", Category(2): "视频", Category(3): "不支持"}
@ -213,23 +221,18 @@ def mock_platform_no_target(app: App):
@pytest.fixture
def mock_platform_no_target_2(app: App):
def mock_platform_no_target_2(app: App, mock_scheduler_conf):
from nonebot_bison.platform.platform import NewMessage
from nonebot_bison.post import Post
from nonebot_bison.types import Category, RawPost, Tag, Target
from nonebot_bison.utils import SchedulerConfig
class MockPlatformSchedConf(SchedulerConfig, name="mock"):
schedule_type = "interval"
schedule_setting = {"seconds": 100}
class MockPlatform(NewMessage):
platform_name = "mock_platform"
name = "Mock Platform"
enabled = True
scheduler_class = "mock"
scheduler = mock_scheduler_conf
is_common = True
enable_tag = True
has_target = False

View File

@ -1,12 +1,19 @@
import typing
from datetime import time
from typing import Type
from nonebug import App
if typing.TYPE_CHECKING:
from nonebot_bison.utils.scheduler_config import SchedulerConfig
async def get_schedule_times(scheduler_class: str, time: int) -> dict[str, int]:
async def get_schedule_times(
scheduler_config: Type["SchedulerConfig"], time: int
) -> dict[str, int]:
from nonebot_bison.scheduler import scheduler_dict
scheduler = scheduler_dict[scheduler_class]
scheduler = scheduler_dict[scheduler_config]
res = {}
for _ in range(time):
schedulable = await scheduler.get_next_schedulable()
@ -19,6 +26,7 @@ async def get_schedule_times(scheduler_class: str, time: int) -> dict[str, int]:
async def test_scheduler_without_time(init_scheduler):
from nonebot_bison.config import config
from nonebot_bison.config.db_config import WeightConfig
from nonebot_bison.platform.bilibili import BilibiliSchedConf
from nonebot_bison.scheduler.manager import init_scheduler
from nonebot_bison.types import Target as T_Target
@ -41,12 +49,12 @@ async def test_scheduler_without_time(init_scheduler):
await init_scheduler()
static_res = await get_schedule_times("bilibili.com", 6)
static_res = await get_schedule_times(BilibiliSchedConf, 6)
assert static_res["bilibili-t1"] == 1
assert static_res["bilibili-t2"] == 2
assert static_res["bilibili-live-t2"] == 3
static_res = await get_schedule_times("bilibili.com", 6)
static_res = await get_schedule_times(BilibiliSchedConf, 6)
assert static_res["bilibili-t1"] == 1
assert static_res["bilibili-t2"] == 2
assert static_res["bilibili-live-t2"] == 3
@ -55,6 +63,7 @@ async def test_scheduler_without_time(init_scheduler):
async def test_scheduler_with_time(app: App, init_scheduler):
from nonebot_bison.config import config, db_config
from nonebot_bison.config.db_config import TimeWeightConfig, WeightConfig
from nonebot_bison.platform.bilibili import BilibiliSchedConf
from nonebot_bison.scheduler.manager import init_scheduler
from nonebot_bison.types import Target as T_Target
@ -85,24 +94,25 @@ async def test_scheduler_with_time(app: App, init_scheduler):
await init_scheduler()
app.monkeypatch.setattr(db_config, "_get_time", lambda: time(1, 30))
static_res = await get_schedule_times("bilibili.com", 6)
static_res = await get_schedule_times(BilibiliSchedConf, 6)
assert static_res["bilibili-t1"] == 1
assert static_res["bilibili-t2"] == 2
assert static_res["bilibili-live-t2"] == 3
static_res = await get_schedule_times("bilibili.com", 6)
static_res = await get_schedule_times(BilibiliSchedConf, 6)
assert static_res["bilibili-t1"] == 1
assert static_res["bilibili-t2"] == 2
assert static_res["bilibili-live-t2"] == 3
app.monkeypatch.setattr(db_config, "_get_time", lambda: time(10, 30))
static_res = await get_schedule_times("bilibili.com", 6)
static_res = await get_schedule_times(BilibiliSchedConf, 6)
assert static_res["bilibili-t2"] == 6
async def test_scheduler_add_new(init_scheduler):
from nonebot_bison.config import config
from nonebot_bison.platform.bilibili import BilibiliSchedConf
from nonebot_bison.scheduler.manager import init_scheduler
from nonebot_bison.types import Target as T_Target
@ -118,12 +128,13 @@ async def test_scheduler_add_new(init_scheduler):
await config.add_subscribe(
123, "group", T_Target("t2"), "target2", "bilibili", [], []
)
stat_res = await get_schedule_times("bilibili.com", 1)
stat_res = await get_schedule_times(BilibiliSchedConf, 1)
assert stat_res["bilibili-t2"] == 1
async def test_schedule_delete(init_scheduler):
from nonebot_bison.config import config
from nonebot_bison.platform.bilibili import BilibiliSchedConf
from nonebot_bison.scheduler.manager import init_scheduler
from nonebot_bison.types import Target as T_Target
@ -136,10 +147,10 @@ async def test_schedule_delete(init_scheduler):
await init_scheduler()
stat_res = await get_schedule_times("bilibili.com", 2)
stat_res = await get_schedule_times(BilibiliSchedConf, 2)
assert stat_res["bilibili-t2"] == 1
assert stat_res["bilibili-t1"] == 1
await config.del_subscribe(123, "group", T_Target("t1"), "bilibili")
stat_res = await get_schedule_times("bilibili.com", 2)
stat_res = await get_schedule_times(BilibiliSchedConf, 2)
assert stat_res["bilibili-t2"] == 2