Skip to content

Commit 14c8175

Browse files
committed
update wechaty setting
1 parent d57456d commit 14c8175

File tree

6 files changed

+139
-100
lines changed

6 files changed

+139
-100
lines changed

src/wechaty/plugin.py

Lines changed: 35 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
from datetime import datetime
3434
from telnetlib import Telnet
3535
import socket
36-
import json
3736
from typing import (
3837
TYPE_CHECKING,
3938
Iterable,
@@ -75,7 +74,7 @@
7574
error
7675
)
7776
from wechaty.types import EndPoint
78-
from wechaty.utils import HookDict
77+
from wechaty.utils import WechatySetting
7978

8079
from .config import config
8180

@@ -139,15 +138,17 @@ def _list_routes_txt(app: Quart) -> List[str]:
139138
rules = list(sorted(rules, key=lambda rule: rule.endpoint))
140139

141140
headers = ("Endpoint", "Methods", "Websocket", "Rule")
142-
rule_methods = [", ".join(sorted(rule.methods)) for rule in rules if rule.methods]
141+
rule_methods = [", ".join(sorted(rule.methods))
142+
for rule in rules if rule.methods]
143143

144144
widths = [
145145
max(len(rule.endpoint) for rule in rules),
146146
max(len(methods) for methods in rule_methods),
147147
len("Websocket"),
148148
max(len(rule.rule) for rule in rules),
149149
]
150-
widths = [max(len(header), width) for header, width in zip(headers, widths)]
150+
widths = [max(len(header), width)
151+
for header, width in zip(headers, widths)]
151152

152153
# pylint: disable=C0209
153154
row = "{{0:<{0}}} | {{1:<{1}}} | {{2:<{2}}} | {{3:<{3}}}".format(*widths)
@@ -158,7 +159,8 @@ def _list_routes_txt(app: Quart) -> List[str]:
158159

159160
for rule, methods in zip(rules, rule_methods):
160161
routes_txt.append(
161-
row.format(rule.endpoint, methods, str(rule.websocket), rule.rule).rstrip()
162+
row.format(rule.endpoint, methods, str(
163+
rule.websocket), rule.rule).rstrip()
162164
)
163165
return routes_txt
164166

@@ -175,7 +177,8 @@ def _signal_handler(*_: Any) -> None: # noqa: N803
175177
for signal_name in ["SIGINT", "SIGTERM", "SIGBREAK"]:
176178
if hasattr(signal, signal_name):
177179
try:
178-
loop.add_signal_handler(getattr(signal, signal_name), _signal_handler)
180+
loop.add_signal_handler(
181+
getattr(signal, signal_name), _signal_handler)
179182
except NotImplementedError:
180183
# Add signal handler may not be implemented on Windows
181184
signal.signal(getattr(signal, signal_name), _signal_handler)
@@ -397,10 +400,9 @@ def __init__(self, options: Optional[WechatyPluginOptions] = None):
397400
self.options = options
398401
self._default_logger: Optional[Logger] = None
399402
self._cache_dir: Optional[str] = None
400-
self.setting_file: str = os.path.join(self.cache_dir, 'setting.json')
401403

402-
if not os.path.exists(self.setting_file):
403-
self.setting = {} # type: ignore
404+
self.setting_file: str = os.path.join(self.cache_dir, 'setting.json')
405+
self._setting_wechaty_setting: WechatySetting = WechatySetting(self.setting_file)
404406

405407
def metadata(self) -> NavMetadata:
406408
"""get the default nav metadata
@@ -417,34 +419,27 @@ def metadata(self) -> NavMetadata:
417419
)
418420

419421
@property
420-
def setting(self) -> HookDict[str, Any]:
422+
def setting(self) -> WechatySetting:
421423
"""get the setting of a plugin"""
422-
with open(self.setting_file, 'r', encoding='utf-8') as f:
423-
setting = json.load(f)
424-
425-
def set_item_hooks(_: str, __: Any, value: dict) -> None:
426-
"""hook set_item event, and save the setting"""
427-
self._save_setting(value)
428-
429-
return HookDict(setting, set_item_hooks=set_item_hooks)
424+
return self._setting_wechaty_setting
430425

431-
def _save_setting(self, value: dict) -> None:
432-
"""update the plugin setting"""
433-
with open(self.setting_file, 'w', encoding='utf-8') as f:
434-
json.dump(value, f, ensure_ascii=False)
435-
436-
@setting.setter # type: ignore
426+
@setting.setter
437427
def setting(self, value: dict) -> None:
438-
"""update the plugin setting"""
439-
self._save_setting(value)
428+
"""update setting with value
429+
430+
Args:
431+
value (dict): the value of setting dict
432+
"""
433+
self._setting_wechaty_setting.save_setting(value)
440434

441435
def get_ui_dir(self) -> Optional[str]:
442436
"""get the ui asset dir
443437
"""
444438
# 1. get the customized ui dir according to the static UI_DIR attribute
445439
if self.UI_DIR:
446440
if os.path.exists(self.UI_DIR):
447-
self.logger.info("finding the UI_DIR<%s> for plugin<%s>", self.UI_DIR, type(self))
441+
self.logger.info(
442+
"finding the UI_DIR<%s> for plugin<%s>", self.UI_DIR, type(self))
448443
return self.UI_DIR
449444

450445
# 2. get the default uidir: ui/dist
@@ -454,7 +449,8 @@ def get_ui_dir(self) -> Optional[str]:
454449
plugin_dir = os.path.dirname(str(plugin_dir_path))
455450
ui_dir = os.path.join(plugin_dir, 'ui')
456451
if os.path.exists(ui_dir):
457-
self.logger.info("finding the UI_DIR<%s> for plugin<%s>", ui_dir, type(self))
452+
self.logger.info(
453+
"finding the UI_DIR<%s> for plugin<%s>", ui_dir, type(self))
458454
return ui_dir
459455

460456
self.logger.warning(
@@ -574,7 +570,8 @@ class WechatyPluginManager: # pylint: disable=too-many-instance-attributes
574570
def __init__(
575571
self, wechaty: Wechaty,
576572
endpoint: EndPoint,
577-
scheduler_options: Optional[Union[AsyncIOScheduler, WechatySchedulerOptions]] = None
573+
scheduler_options: Optional[Union[AsyncIOScheduler,
574+
WechatySchedulerOptions]] = None
578575
):
579576
self._plugins: Dict[str, WechatyPlugin] = OrderedDict()
580577
self._wechaty: Wechaty = wechaty
@@ -591,9 +588,11 @@ def __init__(
591588
scheduler = AsyncIOScheduler()
592589

593590
if isinstance(scheduler_options.job_store, str):
594-
scheduler_options.job_store = SQLAlchemyJobStore(scheduler_options.job_store)
591+
scheduler_options.job_store = SQLAlchemyJobStore(
592+
scheduler_options.job_store)
595593

596-
scheduler.add_jobstore(scheduler_options.job_store, scheduler_options.job_store_alias)
594+
scheduler.add_jobstore(
595+
scheduler_options.job_store, scheduler_options.job_store_alias)
597596
self.scheduler: AsyncIOScheduler = scheduler
598597

599598
self.static_file_cacher = StaticFileCacher([
@@ -670,7 +669,7 @@ async def update_plugin_setting() -> Response:
670669
return error(f'plugin<{name}> not exist ...')
671670

672671
plugin: WechatyPlugin = self._plugins[name]
673-
plugin.setting = data['setting'] # type: ignore
672+
plugin.setting = data['setting']
674673
return success(None)
675674

676675
@app.route("/js/<string:name>", methods=['GET'])
@@ -842,7 +841,8 @@ async def start(self) -> None:
842841
# re-fetch the routes info
843842
routes_txt = _list_routes_txt(self.app)
844843

845-
log.info('============================starting web service========================')
844+
log.info(
845+
'============================starting web service========================')
846846
log.info('starting web service at endpoint: <{%s}:{%d}>', host, port)
847847

848848
shutdown_trigger = await get_shutdown_trigger()
@@ -861,7 +861,8 @@ async def start(self) -> None:
861861
for route_txt in routes_txt:
862862
log.info(route_txt)
863863

864-
log.info('============================web service has started========================')
864+
log.info(
865+
'============================web service has started========================')
865866

866867
# pylint: disable=too-many-locals,too-many-statements,too-many-branches
867868
async def emit_events(self, event_name: str, *args: Any, **kwargs: Any) -> None:

src/wechaty/utils/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22
from .qr_code import qr_terminal
33
from .type_check import default_str
44
from .date_util import timestamp_to_date
5-
from .data_util import HookDict
5+
from .data_util import WechatySetting
66
# from .type_check import type_check
77

88
__all__ = [
99
'qr_terminal',
1010
'default_str',
1111
'timestamp_to_date',
12-
'HookDict'
12+
'WechatySetting'
1313
# 'type_check'
1414
]

src/wechaty/utils/data_util.py

Lines changed: 50 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -19,59 +19,66 @@
1919
limitations under the License.
2020
"""
2121
from __future__ import annotations
22+
import json
2223

24+
import os
2325
from typing import (
24-
Callable,
2526
Any,
26-
Dict,
27-
Iterable,
28-
Iterator,
29-
Optional,
30-
Type,
31-
TypeVar,
32-
MutableMapping,
33-
Generic
3427
)
28+
from collections import UserDict
3529

36-
_KT = TypeVar("_KT")
37-
_VT = TypeVar("_VT")
3830

39-
40-
class HookDict(MutableMapping[_KT, _VT], Generic[_KT, _VT]):
41-
"""add hooks to the dict data
42-
"""
31+
class WechatySetting(UserDict):
32+
"""save setting into file when changed"""
4333
def __init__(
4434
self,
45-
data: Dict[_KT, _VT],
46-
set_item_hooks: Optional[Callable[[_KT, _VT, Dict[_KT, _VT]], None]] = None,
47-
default_type: Type = int,
35+
setting_file: str
4836
):
49-
self._dict_data = data
50-
self._set_item_hooks = set_item_hooks
51-
self.default_type = default_type
37+
"""init wechaty setting"""
38+
super().__init__()
39+
self.setting_file = setting_file
40+
self._init_setting()
41+
self.data = self.read_setting()
5242

53-
def __setitem__(self, key: _KT, value: Any) -> None:
54-
"""triggered by `data[key] = value`"""
55-
self._dict_data[key] = value
56-
if self._set_item_hooks is not None:
57-
self._set_item_hooks(key, value, self._dict_data)
43+
def _init_setting(self):
44+
"""init setting file"""
45+
# 1. init setting dir
46+
setting_dir = os.path.dirname(self.setting_file)
47+
os.makedirs(setting_dir, exist_ok=True)
48+
49+
# 2. init setting file
50+
if not os.path.exists(self.setting_file):
51+
self.save_setting({})
5852

59-
def __getitem__(self, __k: _KT) -> _VT:
60-
"""get item data"""
61-
return self._dict_data.get(__k, self.default_type())
62-
63-
def __delitem__(self, __v: _KT) -> None:
64-
"""del item by key"""
65-
del self._dict_data[__v]
66-
67-
def __iter__(self) -> Iterator[_KT]:
68-
"""get the iterable data of data"""
69-
return iter(self._dict_data)
70-
71-
def __len__(self) -> int:
72-
"""get the length of dict data"""
73-
return len(self._dict_data)
53+
# 3. check the content of setting file
54+
else:
55+
with open(self.setting_file, 'r', encoding='utf-8') as f:
56+
content = f.read().strip()
57+
58+
if not content:
59+
self.save_setting({})
60+
61+
def read_setting(self) -> dict:
62+
"""read the setting from file
63+
64+
Returns:
65+
dict: the data of setting file
66+
"""
67+
with open(self.setting_file, 'r', encoding='utf-8') as f:
68+
data = json.load(f)
69+
return data
70+
71+
def save_setting(self, value: dict) -> None:
72+
"""update the plugin setting"""
73+
with open(self.setting_file, 'w', encoding='utf-8') as f:
74+
json.dump(value, f, ensure_ascii=False)
75+
self.data = value
76+
77+
def __setitem__(self, key: str, value: Any) -> None:
78+
"""triggered by `data[key] = value`"""
79+
self.data[key] = value
80+
self.save_setting(self.data)
7481

7582
def to_dict(self) -> dict:
76-
"""get the source dict data"""
77-
return self._dict_data
83+
"""return the dict data"""
84+
return self.data

tests/plugin_test.py

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
import json
33
import os
44
import tempfile
5+
import unittest
56
from wechaty.plugin import WechatyPlugin
7+
from wechaty.utils.data_util import WechatySetting
68

79

810
def test_setting():
@@ -11,7 +13,9 @@ def test_setting():
1113
plugin = WechatyPlugin()
1214

1315
plugin.setting['unk'] = 11
14-
plugin.setting['count'] += 20
16+
17+
assert 'count' not in plugin.setting
18+
plugin.setting['count'] = 20
1519

1620
# load the setting file
1721
assert os.path.exists(plugin.setting_file)
@@ -22,3 +26,47 @@ def test_setting():
2226
assert data['unk'] == 11
2327
assert data['count'] == 20
2428

29+
class TestWechatySetting(unittest.TestCase):
30+
31+
def setUp(self) -> None:
32+
self.tempdir = tempfile.TemporaryDirectory()
33+
34+
def tearDown(self) -> None:
35+
self.tempdir.cleanup()
36+
37+
38+
def test_simple_init(self):
39+
setting_file = os.path.join(self.tempdir.name, 'simple_setting.json')
40+
wechaty_setting: WechatySetting = WechatySetting(setting_file)
41+
42+
assert os.path.exists(setting_file)
43+
44+
wechaty_setting['a'] = 'a'
45+
assert 'a' in wechaty_setting.read_setting()
46+
assert wechaty_setting.read_setting()['a'] == 'a'
47+
48+
assert 'b' not in wechaty_setting
49+
50+
assert wechaty_setting.get("b", "b") == "b"
51+
52+
wechaty_setting.save_setting({"c": "c"})
53+
assert 'a' not in wechaty_setting
54+
assert 'c' in wechaty_setting
55+
56+
def test_sub_setting(self):
57+
setting_file = os.path.join(self.tempdir.name, "sub", 'simple_setting.json')
58+
wechaty_setting: WechatySetting = WechatySetting(setting_file)
59+
60+
assert os.path.exists(setting_file)
61+
62+
wechaty_setting['a'] = 'a'
63+
assert 'a' in wechaty_setting.read_setting()
64+
assert wechaty_setting.read_setting()['a'] == 'a'
65+
66+
assert 'b' not in wechaty_setting
67+
68+
assert wechaty_setting.get("b", "b") == "b"
69+
70+
wechaty_setting.save_setting({"c": "c"})
71+
assert 'a' not in wechaty_setting
72+
assert 'c' in wechaty_setting

0 commit comments

Comments
 (0)