cmdbox 0.6.3.2__py3-none-any.whl → 0.6.4__py3-none-any.whl

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.

Potentially problematic release.


This version of cmdbox might be problematic. Click here for more details.

Files changed (133) hide show
  1. cmdbox/app/app.py +16 -7
  2. cmdbox/app/common.py +2 -2
  3. cmdbox/app/commons/redis_client.py +1 -1
  4. cmdbox/app/edge.py +1 -1
  5. cmdbox/app/feature.py +1 -1
  6. cmdbox/app/features/cli/cmdbox_audit_createdb.py +224 -224
  7. cmdbox/app/features/cli/cmdbox_audit_delete.py +4 -4
  8. cmdbox/app/features/cli/cmdbox_audit_search.py +4 -4
  9. cmdbox/app/features/cli/cmdbox_audit_write.py +6 -8
  10. cmdbox/app/features/cli/cmdbox_client_file_copy.py +3 -3
  11. cmdbox/app/features/cli/cmdbox_client_file_download.py +3 -3
  12. cmdbox/app/features/cli/cmdbox_client_file_list.py +3 -3
  13. cmdbox/app/features/cli/cmdbox_client_file_mkdir.py +3 -3
  14. cmdbox/app/features/cli/cmdbox_client_file_move.py +3 -3
  15. cmdbox/app/features/cli/cmdbox_client_file_remove.py +3 -3
  16. cmdbox/app/features/cli/cmdbox_client_file_rmdir.py +3 -3
  17. cmdbox/app/features/cli/cmdbox_client_file_upload.py +3 -3
  18. cmdbox/app/features/cli/cmdbox_client_http.py +7 -6
  19. cmdbox/app/features/cli/cmdbox_client_server_info.py +4 -4
  20. cmdbox/app/features/cli/cmdbox_cmd_list.py +3 -3
  21. cmdbox/app/features/cli/cmdbox_cmd_load.py +5 -5
  22. cmdbox/app/features/cli/cmdbox_edge_config.py +1 -1
  23. cmdbox/app/features/cli/cmdbox_edge_start.py +3 -3
  24. cmdbox/app/features/cli/cmdbox_mcp_client.py +174 -174
  25. cmdbox/app/features/cli/cmdbox_mcp_proxy.py +96 -96
  26. cmdbox/app/features/cli/cmdbox_server_list.py +2 -2
  27. cmdbox/app/features/cli/cmdbox_server_start.py +103 -103
  28. cmdbox/app/features/cli/cmdbox_server_stop.py +4 -4
  29. cmdbox/app/features/cli/cmdbox_tts_install.py +307 -0
  30. cmdbox/app/features/cli/cmdbox_tts_say.py +179 -0
  31. cmdbox/app/features/cli/cmdbox_tts_start.py +329 -0
  32. cmdbox/app/features/cli/cmdbox_tts_stop.py +108 -0
  33. cmdbox/app/features/cli/cmdbox_web_apikey_add.py +91 -91
  34. cmdbox/app/features/cli/cmdbox_web_apikey_del.py +91 -91
  35. cmdbox/app/features/cli/cmdbox_web_gencert.py +6 -6
  36. cmdbox/app/features/cli/cmdbox_web_genpass.py +168 -168
  37. cmdbox/app/features/cli/cmdbox_web_group_add.py +94 -94
  38. cmdbox/app/features/cli/cmdbox_web_group_del.py +87 -87
  39. cmdbox/app/features/cli/cmdbox_web_group_edit.py +94 -94
  40. cmdbox/app/features/cli/cmdbox_web_group_list.py +87 -87
  41. cmdbox/app/features/cli/cmdbox_web_start.py +235 -235
  42. cmdbox/app/features/cli/cmdbox_web_stop.py +72 -72
  43. cmdbox/app/features/cli/cmdbox_web_user_add.py +104 -104
  44. cmdbox/app/features/cli/cmdbox_web_user_del.py +87 -87
  45. cmdbox/app/features/cli/cmdbox_web_user_edit.py +104 -104
  46. cmdbox/app/features/cli/cmdbox_web_user_list.py +87 -87
  47. cmdbox/app/filer.py +9 -9
  48. cmdbox/app/mcp.py +15 -7
  49. cmdbox/app/options.py +46 -43
  50. cmdbox/app/server.py +224 -224
  51. cmdbox/extensions/features.yml +3 -0
  52. cmdbox/extensions/sample_project/sample/app/features/cli/sample_client_time.py +2 -2
  53. cmdbox/extensions/sample_project/sample/app/features/cli/sample_server_time.py +3 -3
  54. cmdbox/licenses/LICENSE_Werkzeug_3_1_1_BSD_License.txt +28 -0
  55. cmdbox/licenses/LICENSE_absolufy-imports_0_3_1_MIT_License.txt +21 -0
  56. cmdbox/licenses/LICENSE_cyclopts_3_22_5_Apache_Software_License.txt +201 -0
  57. cmdbox/licenses/LICENSE_isodate_0_7_2_BSD_License.txt +26 -0
  58. cmdbox/licenses/LICENSE_lazy-object-proxy_1_11_0_BSD_License.txt +20 -0
  59. cmdbox/licenses/LICENSE_openapi-core_0_19_5_BSD_License.txt +29 -0
  60. cmdbox/licenses/LICENSE_openapi-schema-validator_0_6_3_BSD_License.txt +29 -0
  61. cmdbox/licenses/LICENSE_openapi-spec-validator_0_7_2_Apache_Software_License.txt +201 -0
  62. cmdbox/licenses/LICENSE_opentelemetry-semantic-conventions_0_57b0_UNKNOWN.txt +201 -0
  63. cmdbox/licenses/LICENSE_parse_1_20_2_MIT_License.txt +19 -0
  64. cmdbox/licenses/LICENSE_pathable_0_4_4_Apache_Software_License.txt +201 -0
  65. cmdbox/licenses/{LICENSE_pillow_11_2_1_UNKNOWN.txt → LICENSE_pillow_11_3_0_UNKNOWN.txt} +393 -3
  66. cmdbox/licenses/LICENSE_pyperclip_1_9_0_BSD_License.txt +27 -0
  67. cmdbox/licenses/LICENSE_rfc3339-validator_0_1_4_MIT_License.txt +22 -0
  68. cmdbox/licenses/LICENSE_rich-rst_1_3_1_MIT_License.txt +7 -0
  69. cmdbox/licenses/{LICENSE_setuptools_65_5_0_MIT_License.txt → LICENSE_setuptools_80_9_0_UNKNOWN.txt} +0 -2
  70. cmdbox/licenses/LICENSE_tokenizers_0_21_4_Apache_Software_License.txt +1 -0
  71. cmdbox/licenses/LICENSE_voicevox_core_0_16_0_MIT.txt +20 -0
  72. cmdbox/licenses/LICENSE_watchdog_6_0_0_Apache_Software_License.txt +16 -0
  73. cmdbox/licenses/{LICENSE_typer_0_16_0_MIT_License.txt → LICENSE_wsproto_1_2_0_MIT_License.txt} +2 -2
  74. cmdbox/licenses/files.txt +56 -42
  75. cmdbox/logconf_cmdbox.yml +104 -0
  76. cmdbox/version.py +2 -2
  77. cmdbox/web/agent.html +8 -2
  78. cmdbox/web/assets/cmdbox/agent.js +182 -1
  79. cmdbox/web/assets/cmdbox/common.js +16 -3
  80. cmdbox/web/assets/cmdbox/svgicon.js +18 -0
  81. {cmdbox-0.6.3.2.dist-info → cmdbox-0.6.4.dist-info}/METADATA +28 -20
  82. {cmdbox-0.6.3.2.dist-info → cmdbox-0.6.4.dist-info}/RECORD +122 -112
  83. {cmdbox-0.6.3.2.dist-info → cmdbox-0.6.4.dist-info}/WHEEL +1 -1
  84. cmdbox/config.yml +0 -3
  85. cmdbox/licenses/LICENSE_httptools_0_6_4_MIT_License.txt +0 -21
  86. cmdbox/licenses/LICENSE_shellingham_1_5_4_ISC_License-ISCL.txt +0 -13
  87. cmdbox/licenses/LICENSE_watchfiles_1_1_0_MIT_License.txt +0 -21
  88. cmdbox/logconf_audit.yml +0 -43
  89. cmdbox/logconf_client.yml +0 -43
  90. cmdbox/logconf_edge.yml +0 -43
  91. cmdbox/logconf_gui.yml +0 -43
  92. cmdbox/logconf_mcp.yml +0 -43
  93. cmdbox/logconf_server.yml +0 -43
  94. cmdbox/logconf_web.yml +0 -43
  95. /cmdbox/licenses/{LICENSE_Authlib_1_6_0_BSD_License.txt → LICENSE_Authlib_1_6_1_BSD_License.txt} +0 -0
  96. /cmdbox/licenses/{LICENSE_SQLAlchemy_2_0_41_MIT.txt → LICENSE_SQLAlchemy_2_0_42_MIT.txt} +0 -0
  97. /cmdbox/licenses/{LICENSE_aiohttp_3_12_13_Apache-2_0.txt → LICENSE_aiohttp_3_12_15_Apache-2_0_AND_MIT.txt} +0 -0
  98. /cmdbox/licenses/{LICENSE_aiosignal_1_3_2_Apache_Software_License.txt → LICENSE_aiosignal_1_4_0_Apache_Software_License.txt} +0 -0
  99. /cmdbox/licenses/{LICENSE_certifi_2025_6_15_Mozilla_Public_License_2_0-MPL_2_0.txt → LICENSE_certifi_2025_7_14_Mozilla_Public_License_2_0-MPL_2_0.txt} +0 -0
  100. /cmdbox/licenses/{LICENSE_cryptography_45_0_4_Apache-2_0_OR_BSD-3-Clause.txt → LICENSE_cryptography_45_0_5_Apache-2_0_OR_BSD-3-Clause.txt} +0 -0
  101. /cmdbox/licenses/{LICENSE_docstring_parser_0_16_MIT_License.txt → LICENSE_docstring_parser_0_17_0_MIT_License.txt} +0 -0
  102. /cmdbox/licenses/{LICENSE_fastapi_0_115_14_MIT_License.txt → LICENSE_fastapi_0_116_1_MIT_License.txt} +0 -0
  103. /cmdbox/licenses/{LICENSE_fastmcp_2_10_1_Apache_Software_License.txt → LICENSE_fastmcp_2_11_0_Apache_Software_License.txt} +0 -0
  104. /cmdbox/licenses/{LICENSE_fsspec_2025_5_1_BSD_License.txt → LICENSE_fsspec_2025_7_0_BSD_License.txt} +0 -0
  105. /cmdbox/licenses/{LICENSE_google-adk_1_5_0_Apache_Software_License.txt → LICENSE_google-adk_1_9_0_Apache_Software_License.txt} +0 -0
  106. /cmdbox/licenses/{LICENSE_google-api-python-client_2_174_0_Apache_Software_License.txt → LICENSE_google-api-python-client_2_177_0_Apache_Software_License.txt} +0 -0
  107. /cmdbox/licenses/{LICENSE_google-cloud-aiplatform_1_100_0_Apache_2_0.txt → LICENSE_google-cloud-aiplatform_1_106_0_Apache_2_0.txt} +0 -0
  108. /cmdbox/licenses/{LICENSE_google-cloud-bigquery_3_34_0_Apache_Software_License.txt → LICENSE_google-cloud-bigquery_3_35_1_Apache_Software_License.txt} +0 -0
  109. /cmdbox/licenses/{LICENSE_google-genai_1_23_0_Apache_Software_License.txt → LICENSE_google-genai_1_28_0_Apache_Software_License.txt} +0 -0
  110. /cmdbox/licenses/{LICENSE_grpcio-status_1_73_1_Apache_Software_License.txt → LICENSE_grpcio-status_1_74_0_Apache_Software_License.txt} +0 -0
  111. /cmdbox/licenses/{LICENSE_grpcio_1_73_1_Apache_Software_License.txt → LICENSE_grpcio_1_74_0_Apache_Software_License.txt} +0 -0
  112. /cmdbox/licenses/{LICENSE_huggingface-hub_0_33_1_Apache_Software_License.txt → LICENSE_huggingface-hub_0_34_3_Apache_Software_License.txt} +0 -0
  113. /cmdbox/licenses/{LICENSE_opentelemetry-api_1_34_1_Apache_Software_License.txt → LICENSE_jsonschema-path_0_3_4_Apache_Software_License.txt} +0 -0
  114. /cmdbox/licenses/{LICENSE_jsonschema_4_24_0_UNKNOWN.txt → LICENSE_jsonschema_4_25_0_UNKNOWN.txt} +0 -0
  115. /cmdbox/licenses/{LICENSE_litellm_1_73_6_MIT_License.txt → LICENSE_litellm_1_74_12_MIT_License.txt} +0 -0
  116. /cmdbox/licenses/{LICENSE_mcp_1_10_1_MIT_License.txt → LICENSE_mcp_1_12_3_MIT_License.txt} +0 -0
  117. /cmdbox/licenses/{LICENSE_multidict_6_6_2_Apache_License_2_0.txt → LICENSE_multidict_6_6_3_Apache_License_2_0.txt} +0 -0
  118. /cmdbox/licenses/{LICENSE_nh3_0_2_21_MIT.txt → LICENSE_nh3_0_3_0_MIT.txt} +0 -0
  119. /cmdbox/licenses/{LICENSE_numpy_2_3_1_BSD_License.txt → LICENSE_numpy_2_3_2_BSD_License.txt} +0 -0
  120. /cmdbox/licenses/{LICENSE_openai_1_93_0_Apache_Software_License.txt → LICENSE_openai_1_98_0_Apache_Software_License.txt} +0 -0
  121. /cmdbox/licenses/{LICENSE_opentelemetry-sdk_1_34_1_Apache_Software_License.txt → LICENSE_opentelemetry-api_1_36_0_UNKNOWN.txt} +0 -0
  122. /cmdbox/licenses/{LICENSE_opentelemetry-semantic-conventions_0_55b1_Apache_Software_License.txt → LICENSE_opentelemetry-sdk_1_36_0_UNKNOWN.txt} +0 -0
  123. /cmdbox/licenses/{LICENSE_tokenizers_0_21_2_Apache_Software_License.txt → LICENSE_pywin32_311_Python_Software_Foundation_License.txt} +0 -0
  124. /cmdbox/licenses/{LICENSE_regex_2024_11_6_Apache_Software_License.txt → LICENSE_regex_2025_7_34_UNKNOWN.txt} +0 -0
  125. /cmdbox/licenses/{LICENSE_rich_14_0_0_MIT_License.txt → LICENSE_rich_14_1_0_MIT_License.txt} +0 -0
  126. /cmdbox/licenses/{LICENSE_rpds-py_0_25_1_MIT.txt → LICENSE_rpds-py_0_26_0_MIT.txt} +0 -0
  127. /cmdbox/licenses/{LICENSE_sse-starlette_2_3_6_BSD_License.txt → LICENSE_sse-starlette_3_0_2_UNKNOWN.txt} +0 -0
  128. /cmdbox/licenses/{LICENSE_starlette_0_46_2_BSD_License.txt → LICENSE_starlette_0_47_2_BSD_License.txt} +0 -0
  129. /cmdbox/licenses/{LICENSE_typing_extensions_4_14_0_UNKNOWN.txt → LICENSE_typing_extensions_4_14_1_UNKNOWN.txt} +0 -0
  130. /cmdbox/licenses/{LICENSE_zope_event_5_1_Zope_Public_License.txt → LICENSE_zope_event_5_1_1_Zope_Public_License.txt} +0 -0
  131. {cmdbox-0.6.3.2.dist-info → cmdbox-0.6.4.dist-info}/entry_points.txt +0 -0
  132. {cmdbox-0.6.3.2.dist-info → cmdbox-0.6.4.dist-info/licenses}/LICENSE +0 -0
  133. {cmdbox-0.6.3.2.dist-info → cmdbox-0.6.4.dist-info}/top_level.txt +0 -0
cmdbox/app/options.py CHANGED
@@ -368,20 +368,7 @@ class Options:
368
368
  """
369
369
  return ftype in self.features_loaded and self.features_loaded[ftype]
370
370
 
371
- def load_features_file(self, ftype:str, func, appcls, ver, logger:logging.Logger=None):
372
- """
373
- フィーチャーファイル(features.yml)を読み込みます。
374
-
375
- Args:
376
- ftype (str): フィーチャータイプ。cli又はweb
377
- func (Any): フィーチャーの処理関数
378
- appcls (Any): アプリケーションクラス
379
- ver (Any): バージョンモジュール
380
- logger (logging.Logger): ロガー
381
- """
382
- # 読込み済みかどうかの判定
383
- if self.is_features_loaded(ftype):
384
- return
371
+ def _load_features_yml(self, ver, logger:logging.Logger=None):
385
372
  # cmdboxを拡張したアプリをカスタマイズするときのfeatures.ymlを読み込む
386
373
  features_yml = Path(f'.{ver.__appid__}/features.yml')
387
374
  if not features_yml.exists() or not features_yml.is_file():
@@ -398,35 +385,51 @@ class Options:
398
385
  logger.debug(f"features.yml data: {yml}")
399
386
  else:
400
387
  yml = self.features_yml_data
401
- if yml is None: return
402
- if 'features' not in yml:
403
- raise Exception('features.yml is invalid. (The root element must be "features".)')
404
- if ftype not in yml['features']:
405
- raise Exception(f'features.yml is invalid. (There is no “{ftype}” in the “features” element.)')
406
- if yml['features'][ftype] is None:
407
- return
408
- if type(yml['features'][ftype]) is not list:
409
- raise Exception(f'features.yml is invalid. (The “features.{ftype} element must be a list. {ftype}={yml["features"][ftype]})')
410
- # featureモジュール読込みの前にagentruleの読み込み
411
- self.load_features_agentrule(logger)
412
- for data in yml['features'][ftype]:
413
- if type(data) is not dict:
414
- raise Exception(f'features.yml is invalid. (The “features.{ftype}” element must be a list element must be a dictionary. data={data})')
415
- if 'package' not in data:
416
- raise Exception(f'features.yml is invalid. (The “package” element must be in the dictionary of the list element of the “features.{ftype}” element. data={data})')
417
- if 'prefix' not in data:
418
- raise Exception(f'features.yml is invalid. (The prefix element must be in the dictionary of the list element of the “features.{ftype}” element. data={data})')
419
- if data['package'] is None or data['package'] == "":
420
- continue
421
- if data['prefix'] is None or data['prefix'] == "":
422
- continue
423
- exclude_modules = []
424
- if 'exclude_modules' in data:
425
- if type(data['exclude_modules']) is not list:
426
- raise Exception(f'features.yml is invalid. (The “exclude_modules” element must be a list element. data={data})')
427
- exclude_modules = data['exclude_modules']
428
- func(data['package'], data['prefix'], exclude_modules, appcls, ver, logger, self.is_features_loaded(ftype))
429
- self.features_loaded[ftype] = True
388
+ return yml
389
+ return None
390
+
391
+ def load_features_file(self, ftype:str, func, appcls, ver, logger:logging.Logger=None):
392
+ """
393
+ フィーチャーファイル(features.yml)を読み込みます。
394
+
395
+ Args:
396
+ ftype (str): フィーチャータイプ。cli又はweb
397
+ func (Any): フィーチャーの処理関数
398
+ appcls (Any): アプリケーションクラス
399
+ ver (Any): バージョンモジュール
400
+ logger (logging.Logger): ロガー
401
+ """
402
+ # 読込み済みかどうかの判定
403
+ if self.is_features_loaded(ftype):
404
+ return
405
+ yml = self._load_features_yml(ver, logger)
406
+ if yml is None: return
407
+ if 'features' not in yml:
408
+ raise Exception('features.yml is invalid. (The root element must be "features".)')
409
+ if ftype not in yml['features']:
410
+ raise Exception(f'features.yml is invalid. (There is no “{ftype}” in the “features” element.)')
411
+ if yml['features'][ftype] is None:
412
+ return
413
+ if type(yml['features'][ftype]) is not list:
414
+ raise Exception(f'features.yml is invalid. (The “features.{ftype} element must be a list. {ftype}={yml["features"][ftype]})')
415
+ for data in yml['features'][ftype]:
416
+ if type(data) is not dict:
417
+ raise Exception(f'features.yml is invalid. (The “features.{ftype}” element must be a list element must be a dictionary. data={data})')
418
+ if 'package' not in data:
419
+ raise Exception(f'features.yml is invalid. (The “package” element must be in the dictionary of the list element of the “features.{ftype}” element. data={data})')
420
+ if 'prefix' not in data:
421
+ raise Exception(f'features.yml is invalid. (The prefix element must be in the dictionary of the list element of the “features.{ftype}” element. data={data})')
422
+ if data['package'] is None or data['package'] == "":
423
+ continue
424
+ if data['prefix'] is None or data['prefix'] == "":
425
+ continue
426
+ exclude_modules = []
427
+ if 'exclude_modules' in data:
428
+ if type(data['exclude_modules']) is not list:
429
+ raise Exception(f'features.yml is invalid. (The “exclude_modules” element must be a list element. data={data})')
430
+ exclude_modules = data['exclude_modules']
431
+ func(data['package'], data['prefix'], exclude_modules, appcls, ver, logger, self.is_features_loaded(ftype))
432
+ self.features_loaded[ftype] = True
430
433
 
431
434
 
432
435
  def load_features_args(self, args_dict:Dict[str, Any]):
cmdbox/app/server.py CHANGED
@@ -1,224 +1,224 @@
1
- from pathlib import Path
2
- from cmdbox.app import common, filer, feature, options
3
- from cmdbox.app.commons import redis_client
4
- from redis import exceptions
5
- from typing import List, Dict, Any
6
- import logging
7
- import redis
8
- import time
9
-
10
-
11
- class Server(filer.Filer):
12
-
13
- def __init__(self, data_dir:Path, logger:logging.Logger, redis_host:str="localhost", redis_port:int=6379, redis_password:str=None, svname:str='server'):
14
- """
15
- Redisサーバーに接続し、クライアントからのコマンドを受信し実行する
16
-
17
- Args:
18
- data_dir (Path): データフォルダのパス
19
- logger (logging): ロガー
20
- redis_host (str): Redisホスト名, by default "localhost"
21
- redis_port (int): Redisポート番号, by default 6379
22
- redis_password (str): Redisパスワード, by default None
23
- svname (str, optional): サーバーのサービス名. by default 'server'
24
- """
25
- super().__init__(data_dir, logger)
26
- if svname.find('-') >= 0:
27
- raise ValueError(f"Server name is invalid. '-' is not allowed. svname={svname}")
28
- self.redis_host = redis_host
29
- self.redis_port = redis_port
30
- self.redis_password = redis_password
31
- self.org_svname = svname
32
- self.svname = f"{svname}-{common.random_string(size=6)}"
33
- self.redis_cli = None
34
- self.sessions:Dict[str, Dict[str, Any]] = {}
35
- self.is_running = False
36
- self.train_thread = None
37
- self.cleaning_interval = 60
38
- if self.logger.level == logging.DEBUG:
39
- self.logger.debug(f"server init parameter: data={self.data_dir} -> {self.data_dir.absolute()}")
40
- self.logger.debug(f"server init parameter: redis_host={self.redis_host}")
41
- self.logger.debug(f"server init parameter: redis_port={self.redis_port}")
42
- self.logger.debug(f"server init parameter: redis_password=********")
43
- self.logger.debug(f"server init parameter: svname={self.svname}")
44
- self.options = options.Options.getInstance()
45
-
46
- def __enter__(self):
47
- self.start_server()
48
- return self
49
-
50
- def __exit__(self, a, b, c):
51
- self.terminate_server()
52
-
53
- def start_server(self, retry_count:int=20, retry_interval:int=5):
54
- """
55
- サーバー処理を開始する
56
- """
57
- self.is_running = False
58
- self.retry_count = retry_count
59
- self.retry_interval = retry_interval
60
- if self.logger.level == logging.DEBUG:
61
- self.logger.debug(f"server start parameter: retry_count={self.retry_count}")
62
- self.logger.debug(f"server start parameter: retry_interval={self.retry_interval}")
63
- self.redis_cli = redis_client.RedisClient(self.logger, host=self.redis_host, port=self.redis_port, password=self.redis_password, svname=self.svname)
64
- if self.redis_cli.check_server(find_svname=False, retry_count=self.retry_count, retry_interval=self.retry_interval, outstatus=True):
65
- self.is_running = True
66
- self._run_server()
67
-
68
- def list_server(self) -> Dict[str, List[Dict[str, Any]]]:
69
- """
70
- 起動しているサーバーリストを取得する
71
-
72
- Returns:
73
- Dict[str, List[Dict[str, Any]]]: サーバーのリスト
74
- """
75
- if self.redis_cli is None:
76
- self.redis_cli = redis_client.RedisClient(self.logger, host=self.redis_host, port=self.redis_port, password=self.redis_password, svname=self.svname)
77
- svlist = self.redis_cli.list_server()
78
- if len(svlist) <= 0:
79
- return dict(warn="No server is running.")
80
- return dict(success=svlist)
81
-
82
- def _clean_server(self):
83
- """
84
- Redisサーバーに残っている停止済みのサーバーキーを削除する
85
- """
86
- hblist = self.redis_cli.keys("hb-*")
87
- for hb in hblist:
88
- hb = hb.decode()
89
- try:
90
- v = self.redis_cli.hget(hb, 'ctime')
91
- if v is None:
92
- continue
93
- except exceptions.ResponseError:
94
- self.logger.warning(f"Failed to get ctime. {hb}", exc_info=True)
95
- continue
96
- tm = time.time() - float(v)
97
- if tm > self.cleaning_interval:
98
- self.redis_cli.delete(hb)
99
- self.redis_cli.delete(hb.replace("hb-", "sv-"))
100
-
101
- def _clean_reskey(self):
102
- """
103
- Redisサーバーに残っている停止済みのクライアントキーを削除する
104
- """
105
- rlist = self.redis_cli.keys("cl-*")
106
- for reskey in rlist:
107
- try:
108
- tm = int(reskey.decode().split("-")[2])
109
- if time.time() - tm > self.cleaning_interval:
110
- self.redis_cli.delete(reskey)
111
- except Exception as e:
112
- self.redis_cli.delete(reskey)
113
-
114
- def _run_server(self):
115
- self.logger.info(f"start server. svname={self.svname}")
116
- ltime = time.time()
117
- receive_cnt = 0
118
- sccess_cnt = 0
119
- warn_cnt = 0
120
- error_cnt = 0
121
- self.redis_cli.hset(self.redis_cli.hbname, 'receive_cnt', receive_cnt)
122
- self.redis_cli.hset(self.redis_cli.hbname, 'sccess_cnt', sccess_cnt)
123
- self.redis_cli.hset(self.redis_cli.hbname, 'warn_cnt', warn_cnt)
124
- self.redis_cli.hset(self.redis_cli.hbname, 'error_cnt', error_cnt)
125
-
126
- def _publish(msg_str):
127
- # 各サーバーにメッセージを配布する
128
- hblist = self.redis_cli.keys(f"hb-{self.org_svname}-*")
129
- for hb in hblist:
130
- hb = hb.decode()
131
- sv = hb.replace("hb-", "sv-")
132
- self.redis_cli.rpush(sv, msg_str)
133
-
134
- while self.is_running:
135
- try:
136
- msg = None
137
- # ブロッキングリストから要素を取り出す
138
- ctime = time.time()
139
- self.redis_cli.hset(self.redis_cli.hbname, 'ctime', ctime)
140
- self.redis_cli.hset(self.redis_cli.hbname, 'status', 'ready')
141
- result = self.redis_cli.blpop(self.redis_cli.svname)
142
- if ctime - ltime > self.cleaning_interval:
143
- self._clean_server()
144
- self._clean_reskey()
145
- ltime = ctime
146
- to_cluster = False
147
- if result is None or len(result) <= 0:
148
- # クラスター宛メッセージがあるか確認する
149
- result = self.redis_cli.blpop(f"sv-{self.org_svname}")
150
- if result is None or len(result) <= 0:
151
- time.sleep(1)
152
- continue
153
- to_cluster = True
154
- msg_str = result[1].decode()
155
- msg = msg_str.split(' ')
156
- if len(msg) <= 0:
157
- time.sleep(1)
158
- continue
159
-
160
- st = None
161
- receive_cnt += 1
162
- self.redis_cli.hset(self.redis_cli.hbname, 'receive_cnt', receive_cnt)
163
- self.redis_cli.hset(self.redis_cli.hbname, 'status', 'processing')
164
-
165
- svcmd_feature:feature.Feature = self.options.get_svcmd_feature(msg[0])
166
- if svcmd_feature is not None:
167
- if to_cluster and svcmd_feature.is_cluster_redirect():
168
- _publish(msg_str)
169
- continue
170
- if msg[0] == 'stop_server':
171
- self.is_running = False
172
- st = svcmd_feature.svrun(self.data_dir, self.logger, self.redis_cli, msg, self.sessions)
173
- else:
174
- self.logger.warning(f"Unknown command {msg}")
175
- st = self.RESP_WARN
176
-
177
- if st==self.RESP_SCCESS:
178
- sccess_cnt += 1
179
- self.redis_cli.hset(self.redis_cli.hbname, 'sccess_cnt', sccess_cnt)
180
- elif st==self.RESP_WARN:
181
- warn_cnt += 1
182
- self.redis_cli.hset(self.redis_cli.hbname, 'warn_cnt', warn_cnt)
183
- elif st==self.RESP_ERROR:
184
- error_cnt += 1
185
- self.redis_cli.hset(self.redis_cli.hbname, 'error_cnt', error_cnt)
186
- self.redis_cli.hset(self.redis_cli.hbname, 'ctime', time.time())
187
- except exceptions.TimeoutError:
188
- pass
189
- except exceptions.ConnectionError as e:
190
- self.logger.warning(f"Connection to the server was lost. {e}", exc_info=True)
191
- if not self.redis_cli.check_server(find_svname=False, retry_count=self.retry_count, retry_interval=self.retry_interval, outstatus=True):
192
- self.is_running = False
193
- break
194
- except OSError as e:
195
- self.logger.warning(f"OSError. {e}. This message is not executable in the server environment. ({msg})", exc_info=True)
196
- if msg is not None and len(msg) > 1:
197
- self.redis_cli.rpush(msg[1], dict(warn=f"OSError. {e}. This message is not executable in the server environment. ({msg[0]})"))
198
- error_cnt += 1
199
- self.redis_cli.hset(self.redis_cli.hbname, 'error_cnt', error_cnt)
200
- pass
201
- except IndexError as e:
202
- self.logger.warning(f"IndexError. {e}. The message received by the server is invalid. ({msg})", exc_info=True)
203
- if msg is not None and len(msg) > 1:
204
- self.redis_cli.rpush(msg[1], dict(warn=f"IndexError. {e}. The message received by the server is invalid. ({msg[0]})"))
205
- error_cnt += 1
206
- self.redis_cli.hset(self.redis_cli.hbname, 'error_cnt', error_cnt)
207
- pass
208
- except KeyboardInterrupt as e:
209
- self.is_running = False
210
- break
211
- except Exception as e:
212
- self.logger.warning(f"Unknown error occurred. {e}. Service will be stopped due to unknown cause.({msg})", exc_info=True)
213
- self.is_running = False
214
- break
215
- self.redis_cli.delete(self.redis_cli.svname)
216
- self.redis_cli.delete(self.redis_cli.hbname)
217
- self.logger.info(f"stop server. svname={self.redis_cli.svname}")
218
-
219
- def terminate_server(self):
220
- """
221
- サーバー処理を終了する
222
- """
223
- self.redis_cli.close()
224
- self.logger.info(f"terminate server.")
1
+ from pathlib import Path
2
+ from cmdbox.app import common, filer, feature, options
3
+ from cmdbox.app.commons import redis_client
4
+ from redis import exceptions
5
+ from typing import List, Dict, Any
6
+ import logging
7
+ import redis
8
+ import time
9
+
10
+
11
+ class Server(filer.Filer):
12
+
13
+ def __init__(self, data_dir:Path, logger:logging.Logger, redis_host:str="localhost", redis_port:int=6379, redis_password:str=None, svname:str='server'):
14
+ """
15
+ Redisサーバーに接続し、クライアントからのコマンドを受信し実行する
16
+
17
+ Args:
18
+ data_dir (Path): データフォルダのパス
19
+ logger (logging): ロガー
20
+ redis_host (str): Redisホスト名, by default "localhost"
21
+ redis_port (int): Redisポート番号, by default 6379
22
+ redis_password (str): Redisパスワード, by default None
23
+ svname (str, optional): サーバーのサービス名. by default 'server'
24
+ """
25
+ super().__init__(data_dir, logger)
26
+ if svname.find('-') >= 0:
27
+ raise ValueError(f"Server name is invalid. '-' is not allowed. svname={svname}")
28
+ self.redis_host = redis_host
29
+ self.redis_port = redis_port
30
+ self.redis_password = redis_password
31
+ self.org_svname = svname
32
+ self.svname = f"{svname}-{common.random_string(size=6)}"
33
+ self.redis_cli = None
34
+ self.sessions:Dict[str, Dict[str, Any]] = {}
35
+ self.is_running = False
36
+ self.train_thread = None
37
+ self.cleaning_interval = 60
38
+ if self.logger.level == logging.DEBUG:
39
+ self.logger.debug(f"server init parameter: data={self.data_dir} -> {self.data_dir.absolute()}")
40
+ self.logger.debug(f"server init parameter: redis_host={self.redis_host}")
41
+ self.logger.debug(f"server init parameter: redis_port={self.redis_port}")
42
+ self.logger.debug(f"server init parameter: redis_password=********")
43
+ self.logger.debug(f"server init parameter: svname={self.svname}")
44
+ self.options = options.Options.getInstance()
45
+
46
+ def __enter__(self):
47
+ self.start_server()
48
+ return self
49
+
50
+ def __exit__(self, a, b, c):
51
+ self.terminate_server()
52
+
53
+ def start_server(self, retry_count:int=20, retry_interval:int=5):
54
+ """
55
+ サーバー処理を開始する
56
+ """
57
+ self.is_running = False
58
+ self.retry_count = retry_count
59
+ self.retry_interval = retry_interval
60
+ if self.logger.level == logging.DEBUG:
61
+ self.logger.debug(f"server start parameter: retry_count={self.retry_count}")
62
+ self.logger.debug(f"server start parameter: retry_interval={self.retry_interval}")
63
+ self.redis_cli = redis_client.RedisClient(self.logger, host=self.redis_host, port=self.redis_port, password=self.redis_password, svname=self.svname)
64
+ if self.redis_cli.check_server(find_svname=False, retry_count=self.retry_count, retry_interval=self.retry_interval, outstatus=True):
65
+ self.is_running = True
66
+ self._run_server()
67
+
68
+ def list_server(self) -> Dict[str, List[Dict[str, Any]]]:
69
+ """
70
+ 起動しているサーバーリストを取得する
71
+
72
+ Returns:
73
+ Dict[str, List[Dict[str, Any]]]: サーバーのリスト
74
+ """
75
+ if self.redis_cli is None:
76
+ self.redis_cli = redis_client.RedisClient(self.logger, host=self.redis_host, port=self.redis_port, password=self.redis_password, svname=self.svname)
77
+ svlist = self.redis_cli.list_server()
78
+ if len(svlist) <= 0:
79
+ return dict(warn="No server is running.")
80
+ return dict(success=svlist)
81
+
82
+ def _clean_server(self):
83
+ """
84
+ Redisサーバーに残っている停止済みのサーバーキーを削除する
85
+ """
86
+ hblist = self.redis_cli.keys("hb-*")
87
+ for hb in hblist:
88
+ hb = hb.decode()
89
+ try:
90
+ v = self.redis_cli.hget(hb, 'ctime')
91
+ if v is None:
92
+ continue
93
+ except exceptions.ResponseError:
94
+ self.logger.warning(f"Failed to get ctime. {hb}", exc_info=True)
95
+ continue
96
+ tm = time.time() - float(v)
97
+ if tm > self.cleaning_interval:
98
+ self.redis_cli.delete(hb)
99
+ self.redis_cli.delete(hb.replace("hb-", "sv-"))
100
+
101
+ def _clean_reskey(self):
102
+ """
103
+ Redisサーバーに残っている停止済みのクライアントキーを削除する
104
+ """
105
+ rlist = self.redis_cli.keys("cl-*")
106
+ for reskey in rlist:
107
+ try:
108
+ tm = int(reskey.decode().split("-")[2])
109
+ if time.time() - tm > self.cleaning_interval:
110
+ self.redis_cli.delete(reskey)
111
+ except Exception as e:
112
+ self.redis_cli.delete(reskey)
113
+
114
+ def _run_server(self):
115
+ self.logger.info(f"start server. svname={self.svname}")
116
+ ltime = time.time()
117
+ receive_cnt = 0
118
+ sccess_cnt = 0
119
+ warn_cnt = 0
120
+ error_cnt = 0
121
+ self.redis_cli.hset(self.redis_cli.hbname, 'receive_cnt', receive_cnt)
122
+ self.redis_cli.hset(self.redis_cli.hbname, 'sccess_cnt', sccess_cnt)
123
+ self.redis_cli.hset(self.redis_cli.hbname, 'warn_cnt', warn_cnt)
124
+ self.redis_cli.hset(self.redis_cli.hbname, 'error_cnt', error_cnt)
125
+
126
+ def _publish(msg_str):
127
+ # 各サーバーにメッセージを配布する
128
+ hblist = self.redis_cli.keys(f"hb-{self.org_svname}-*")
129
+ for hb in hblist:
130
+ hb = hb.decode()
131
+ sv = hb.replace("hb-", "sv-")
132
+ self.redis_cli.rpush(sv, msg_str)
133
+
134
+ while self.is_running:
135
+ try:
136
+ msg = None
137
+ # ブロッキングリストから要素を取り出す
138
+ ctime = time.time()
139
+ self.redis_cli.hset(self.redis_cli.hbname, 'ctime', ctime)
140
+ self.redis_cli.hset(self.redis_cli.hbname, 'status', 'ready')
141
+ result = self.redis_cli.blpop(self.redis_cli.svname)
142
+ if ctime - ltime > self.cleaning_interval:
143
+ self._clean_server()
144
+ self._clean_reskey()
145
+ ltime = ctime
146
+ to_cluster = False
147
+ if result is None or len(result) <= 0:
148
+ # クラスター宛メッセージがあるか確認する
149
+ result = self.redis_cli.blpop(f"sv-{self.org_svname}")
150
+ if result is None or len(result) <= 0:
151
+ time.sleep(1)
152
+ continue
153
+ to_cluster = True
154
+ msg_str = result[1].decode()
155
+ msg = msg_str.split(' ')
156
+ if len(msg) <= 0:
157
+ time.sleep(1)
158
+ continue
159
+
160
+ st = None
161
+ receive_cnt += 1
162
+ self.redis_cli.hset(self.redis_cli.hbname, 'receive_cnt', receive_cnt)
163
+ self.redis_cli.hset(self.redis_cli.hbname, 'status', 'processing')
164
+
165
+ svcmd_feature:feature.Feature = self.options.get_svcmd_feature(msg[0])
166
+ if svcmd_feature is not None:
167
+ if to_cluster and svcmd_feature.is_cluster_redirect():
168
+ _publish(msg_str)
169
+ continue
170
+ if msg[0] == 'stop_server':
171
+ self.is_running = False
172
+ st = svcmd_feature.svrun(self.data_dir, self.logger, self.redis_cli, msg, self.sessions)
173
+ else:
174
+ self.logger.warning(f"Unknown command {msg}")
175
+ st = self.RESP_WARN
176
+
177
+ if st==self.RESP_SUCCESS:
178
+ sccess_cnt += 1
179
+ self.redis_cli.hset(self.redis_cli.hbname, 'sccess_cnt', sccess_cnt)
180
+ elif st==self.RESP_WARN:
181
+ warn_cnt += 1
182
+ self.redis_cli.hset(self.redis_cli.hbname, 'warn_cnt', warn_cnt)
183
+ elif st==self.RESP_ERROR:
184
+ error_cnt += 1
185
+ self.redis_cli.hset(self.redis_cli.hbname, 'error_cnt', error_cnt)
186
+ self.redis_cli.hset(self.redis_cli.hbname, 'ctime', time.time())
187
+ except exceptions.TimeoutError:
188
+ pass
189
+ except exceptions.ConnectionError as e:
190
+ self.logger.warning(f"Connection to the server was lost. {e}", exc_info=True)
191
+ if not self.redis_cli.check_server(find_svname=False, retry_count=self.retry_count, retry_interval=self.retry_interval, outstatus=True):
192
+ self.is_running = False
193
+ break
194
+ except OSError as e:
195
+ self.logger.warning(f"OSError. {e}. This message is not executable in the server environment. ({msg})", exc_info=True)
196
+ if msg is not None and len(msg) > 1:
197
+ self.redis_cli.rpush(msg[1], dict(warn=f"OSError. {e}. This message is not executable in the server environment. ({msg[0]})"))
198
+ error_cnt += 1
199
+ self.redis_cli.hset(self.redis_cli.hbname, 'error_cnt', error_cnt)
200
+ pass
201
+ except IndexError as e:
202
+ self.logger.warning(f"IndexError. {e}. The message received by the server is invalid. ({msg})", exc_info=True)
203
+ if msg is not None and len(msg) > 1:
204
+ self.redis_cli.rpush(msg[1], dict(warn=f"IndexError. {e}. The message received by the server is invalid. ({msg[0]})"))
205
+ error_cnt += 1
206
+ self.redis_cli.hset(self.redis_cli.hbname, 'error_cnt', error_cnt)
207
+ pass
208
+ except KeyboardInterrupt as e:
209
+ self.is_running = False
210
+ break
211
+ except Exception as e:
212
+ self.logger.warning(f"Unknown error occurred. {e}. Service will be stopped due to unknown cause.({msg})", exc_info=True)
213
+ self.is_running = False
214
+ break
215
+ self.redis_cli.delete(self.redis_cli.svname)
216
+ self.redis_cli.delete(self.redis_cli.hbname)
217
+ self.logger.info(f"stop server. svname={self.redis_cli.svname}")
218
+
219
+ def terminate_server(self):
220
+ """
221
+ サーバー処理を終了する
222
+ """
223
+ self.redis_cli.close()
224
+ self.logger.info(f"terminate server.")
@@ -46,6 +46,9 @@ agentrule: # Specifies a list of rules that determi
46
46
  - mode: client
47
47
  cmds: [http]
48
48
  rule: allow
49
+ - mode: tts
50
+ cmds: [say]
51
+ rule: allow
49
52
  audit:
50
53
  enabled: true # Specify whether to enable the audit function.
51
54
  write:
@@ -60,8 +60,8 @@ class ClientTime(feature.Feature):
60
60
  ret = dict(success=dict(data=dt.strftime('%Y-%m-%d %H:%M:%S')))
61
61
  common.print_format(ret, args.format, tm, args.output_json, args.output_json_append, pf=pf)
62
62
  if 'success' not in ret:
63
- return 1, ret, None
64
- return 0, ret, None
63
+ return self.RESP_WARN, ret, None
64
+ return self.RESP_SUCCESS, ret, None
65
65
 
66
66
  def edgerun(self, opt, tool, logger, timeout, prevres = None):
67
67
  """
@@ -92,8 +92,8 @@ class ServerTime(feature.Feature):
92
92
  retry_count=args.retry_count, retry_interval=args.retry_interval, timeout=args.timeout)
93
93
  common.print_format(ret, args.format, tm, args.output_json, args.output_json_append, pf=pf)
94
94
  if 'success' not in ret:
95
- return 1, ret, None
96
- return 0, ret, None
95
+ return self.RESP_WARN, ret, None
96
+ return self.RESP_SUCCESS, ret, None
97
97
 
98
98
  def is_cluster_redirect(self):
99
99
  """
@@ -124,7 +124,7 @@ class ServerTime(feature.Feature):
124
124
  dt = datetime.datetime.now(tz)
125
125
  ret = dict(success=dict(data=dt.strftime('%Y-%m-%d %H:%M:%S')))
126
126
  redis_cli.rpush(msg[1], ret)
127
- return self.RESP_SCCESS
127
+ return self.RESP_SUCCESS
128
128
 
129
129
  def edgerun(self, opt, tool, logger, timeout, prevres = None):
130
130
  """
@@ -0,0 +1,28 @@
1
+ Copyright 2007 Pallets
2
+
3
+ Redistribution and use in source and binary forms, with or without
4
+ modification, are permitted provided that the following conditions are
5
+ met:
6
+
7
+ 1. Redistributions of source code must retain the above copyright
8
+ notice, this list of conditions and the following disclaimer.
9
+
10
+ 2. Redistributions in binary form must reproduce the above copyright
11
+ notice, this list of conditions and the following disclaimer in the
12
+ documentation and/or other materials provided with the distribution.
13
+
14
+ 3. Neither the name of the copyright holder nor the names of its
15
+ contributors may be used to endorse or promote products derived from
16
+ this software without specific prior written permission.
17
+
18
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
21
+ PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22
+ HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
24
+ TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
25
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
26
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.