cmdbox 0.6.4.2__py3-none-any.whl → 0.6.6__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.
- cmdbox/app/app.py +7 -0
- cmdbox/app/client.py +384 -383
- cmdbox/app/common.py +85 -7
- cmdbox/app/commons/convert.py +3 -1
- cmdbox/app/edge.py +12 -12
- cmdbox/app/feature.py +1 -1
- cmdbox/app/features/cli/{cmdbox_vision_install.py → _cmdbox_vision_install.py} +2 -10
- cmdbox/app/features/cli/_cmdbox_vision_predict.py +487 -0
- cmdbox/app/features/cli/{cmdbox_vision_start.py → _cmdbox_vision_start.py} +5 -1
- cmdbox/app/features/cli/cmdbox_audit_createdb.py +1 -11
- cmdbox/app/features/cli/cmdbox_audit_delete.py +0 -9
- cmdbox/app/features/cli/cmdbox_audit_search.py +0 -9
- cmdbox/app/features/cli/cmdbox_audit_write.py +0 -9
- cmdbox/app/features/cli/cmdbox_cmd_list.py +3 -3
- cmdbox/app/features/cli/cmdbox_cmd_load.py +3 -3
- cmdbox/app/features/cli/cmdbox_excel_cell_details.py +436 -0
- cmdbox/app/features/cli/cmdbox_excel_cell_search.py +276 -0
- cmdbox/app/features/cli/cmdbox_excel_cell_values.py +258 -0
- cmdbox/app/features/cli/cmdbox_excel_sheet_list.py +159 -0
- cmdbox/app/features/cli/cmdbox_tts_install.py +4 -11
- cmdbox/app/features/cli/cmdbox_tts_say.py +2 -10
- cmdbox/app/features/cli/cmdbox_tts_start.py +0 -9
- cmdbox/app/features/cli/cmdbox_tts_stop.py +0 -9
- cmdbox/app/features/cli/cmdbox_web_apikey_add.py +3 -3
- cmdbox/app/features/cli/cmdbox_web_apikey_del.py +3 -3
- cmdbox/app/features/cli/cmdbox_web_group_add.py +3 -3
- cmdbox/app/features/cli/cmdbox_web_group_del.py +3 -3
- cmdbox/app/features/cli/cmdbox_web_group_edit.py +3 -3
- cmdbox/app/features/cli/cmdbox_web_group_list.py +3 -3
- cmdbox/app/features/cli/cmdbox_web_start.py +10 -10
- cmdbox/app/features/cli/cmdbox_web_user_add.py +3 -3
- cmdbox/app/features/cli/cmdbox_web_user_del.py +3 -3
- cmdbox/app/features/cli/cmdbox_web_user_edit.py +3 -3
- cmdbox/app/features/cli/cmdbox_web_user_list.py +3 -3
- cmdbox/app/features/cli/excel_base.py +301 -0
- cmdbox/app/features/web/cmdbox_web_exec_cmd.py +12 -14
- cmdbox/app/filer.py +5 -2
- cmdbox/app/mcp.py +4 -3
- cmdbox/app/options.py +8 -0
- cmdbox/app/web.py +58 -39
- cmdbox/extensions/features.yml +3 -0
- cmdbox/extensions/sample_project/sample/app/features/cli/sample_server_time.py +0 -9
- cmdbox/licenses/LICENSE_Mako_1_3_10_MIT_License.txt +19 -0
- cmdbox/licenses/LICENSE_alembic_1_16_5_UNKNOWN.txt +19 -0
- cmdbox/licenses/{LICENSE_cffi_1_17_1_MIT_License.txt → LICENSE_cffi_2_0_0_UNKNOWN.txt} +2 -5
- cmdbox/licenses/LICENSE_debugpy_1_8_17_MIT_License.txt +24 -0
- cmdbox/licenses/LICENSE_et_xmlfile_2_0_0_MIT_License.txt +298 -0
- cmdbox/licenses/LICENSE_fastuuid_0_13_5_BSD_License.txt +29 -0
- cmdbox/licenses/LICENSE_google-cloud-monitoring_2_27_2_Apache_Software_License.txt +202 -0
- cmdbox/licenses/LICENSE_google-cloud-spanner_3_58_0_Apache_Software_License.txt +202 -0
- cmdbox/licenses/LICENSE_google-genai_1_40_0_Apache_Software_License.txt +202 -0
- cmdbox/licenses/LICENSE_grpc-interceptor_0_15_4_MIT_License.txt +21 -0
- cmdbox/licenses/{LICENSE_lazy-object-proxy_1_11_0_BSD_License.txt → LICENSE_lazy-object-proxy_1_12_0_UNKNOWN.txt} +1 -1
- cmdbox/licenses/LICENSE_openpyxl_3_1_5_MIT_License.txt +23 -0
- cmdbox/licenses/LICENSE_opentelemetry-exporter-otlp-proto-common_1_37_0_UNKNOWN.txt +201 -0
- cmdbox/licenses/LICENSE_opentelemetry-exporter-otlp-proto-http_1_37_0_UNKNOWN.txt +201 -0
- cmdbox/licenses/LICENSE_opentelemetry-proto_1_37_0_UNKNOWN.txt +201 -0
- cmdbox/licenses/LICENSE_opentelemetry-sdk_1_37_0_UNKNOWN.txt +201 -0
- cmdbox/licenses/LICENSE_opentelemetry-semantic-conventions_0_58b0_UNKNOWN.txt +201 -0
- cmdbox/licenses/LICENSE_sqlalchemy-spanner_1_16_0_Apache_Software_License.txt +202 -0
- cmdbox/licenses/LICENSE_sqlparse_0_5_3_BSD_License.txt +25 -0
- cmdbox/licenses/{LICENSE_uvicorn_0_35_0_BSD_License.txt → LICENSE_uvicorn_0_37_0_BSD_License.txt} +2 -1
- cmdbox/licenses/files.txt +82 -71
- cmdbox/version.py +2 -2
- cmdbox/web/assets/cmdbox/svgicon.js +9 -0
- {cmdbox-0.6.4.2.dist-info → cmdbox-0.6.6.dist-info}/METADATA +29 -29
- {cmdbox-0.6.4.2.dist-info → cmdbox-0.6.6.dist-info}/RECORD +133 -117
- cmdbox/app/features/cli/cmdbox_vision_predict.py +0 -192
- cmdbox/licenses/LICENSE_APScheduler_3_11_0_MIT_License.txt +0 -19
- cmdbox/licenses/LICENSE_backoff_2_2_1_MIT_License.txt +0 -21
- cmdbox/licenses/LICENSE_fastapi-sso_0_18_0_MIT_License.txt +0 -21
- cmdbox/licenses/LICENSE_litellm-enterprise_0_1_19_UNKNOWN.txt +0 -37
- cmdbox/licenses/LICENSE_oauthlib_3_3_1_BSD-3-Clause.txt +0 -27
- cmdbox/licenses/LICENSE_orjson_3_11_1_Apache_Software_License-MIT_License.txt +0 -201
- /cmdbox/licenses/{LICENSE_Authlib_1_6_1_BSD_License.txt → LICENSE_Authlib_1_6_5_BSD_License.txt} +0 -0
- /cmdbox/licenses/{LICENSE_MarkupSafe_3_0_2_BSD_License.txt → LICENSE_MarkupSafe_3_0_3_UNKNOWN.txt} +0 -0
- /cmdbox/licenses/{LICENSE_PyYAML_6_0_2_MIT_License.txt → LICENSE_PyYAML_6_0_3_MIT_License.txt} +0 -0
- /cmdbox/licenses/{LICENSE_anyio_4_10_0_UNKNOWN.txt → LICENSE_anyio_4_11_0_UNKNOWN.txt} +0 -0
- /cmdbox/licenses/{LICENSE_cachetools_5_5_2_MIT_License.txt → LICENSE_cachetools_6_2_0_MIT_License.txt} +0 -0
- /cmdbox/licenses/{LICENSE_click_8_2_1_UNKNOWN.txt → LICENSE_click_8_3_0_UNKNOWN.txt} +0 -0
- /cmdbox/licenses/{LICENSE_cryptography_45_0_6_Apache-2_0_OR_BSD-3-Clause.txt → LICENSE_cryptography_46_0_2_UNKNOWN.txt} +0 -0
- /cmdbox/licenses/{LICENSE_cyclopts_3_22_5_Apache_Software_License.txt → LICENSE_cyclopts_3_24_0_Apache_Software_License.txt} +0 -0
- /cmdbox/licenses/{LICENSE_dnspython_2_7_0_ISC_License-ISCL.txt → LICENSE_dnspython_2_8_0_ISC_License-ISCL.txt} +0 -0
- /cmdbox/licenses/{LICENSE_email_validator_2_2_0_The_Unlicense-Unlicense.txt → LICENSE_email-validator_2_3_0_The_Unlicense-Unlicense.txt} +0 -0
- /cmdbox/licenses/{LICENSE_fastapi_0_116_1_MIT_License.txt → LICENSE_fastapi_0_118_0_MIT_License.txt} +0 -0
- /cmdbox/licenses/{LICENSE_fastmcp_2_11_3_Apache_Software_License.txt → LICENSE_fastmcp_2_12_4_Apache_Software_License.txt} +0 -0
- /cmdbox/licenses/{LICENSE_filelock_3_18_0_The_Unlicense-Unlicense.txt → LICENSE_filelock_3_19_1_The_Unlicense-Unlicense.txt} +0 -0
- /cmdbox/licenses/{LICENSE_fsspec_2025_7_0_BSD_License.txt → LICENSE_fsspec_2025_9_0_UNKNOWN.txt} +0 -0
- /cmdbox/licenses/{LICENSE_gevent_25_5_1_MIT.txt → LICENSE_gevent_25_9_1_MIT.txt} +0 -0
- /cmdbox/licenses/{LICENSE_google-adk_1_10_0_Apache_Software_License.txt → LICENSE_google-adk_1_15_1_Apache_Software_License.txt} +0 -0
- /cmdbox/licenses/{LICENSE_google-api-core_2_25_1_Apache_Software_License.txt → LICENSE_google-api-core_2_25_2_Apache_Software_License.txt} +0 -0
- /cmdbox/licenses/{LICENSE_google-api-python-client_2_178_0_Apache_Software_License.txt → LICENSE_google-api-python-client_2_184_0_Apache_Software_License.txt} +0 -0
- /cmdbox/licenses/{LICENSE_google-auth_2_40_3_Apache_Software_License.txt → LICENSE_google-auth_2_41_1_Apache_Software_License.txt} +0 -0
- /cmdbox/licenses/{LICENSE_google-cloud-aiplatform_1_108_0_Apache_2_0.txt → LICENSE_google-cloud-aiplatform_1_119_0_Apache_2_0.txt} +0 -0
- /cmdbox/licenses/{LICENSE_google-cloud-bigquery_3_35_1_Apache_Software_License.txt → LICENSE_google-cloud-bigquery_3_38_0_Apache_Software_License.txt} +0 -0
- /cmdbox/licenses/{LICENSE_google-genai_1_29_0_Apache_Software_License.txt → LICENSE_google-cloud-bigtable_2_32_0_Apache_Software_License.txt} +0 -0
- /cmdbox/licenses/{LICENSE_grpcio-status_1_74_0_Apache_Software_License.txt → LICENSE_grpcio-status_1_75_1_Apache_Software_License.txt} +0 -0
- /cmdbox/licenses/{LICENSE_grpcio_1_74_0_Apache_Software_License.txt → LICENSE_grpcio_1_75_1_Apache_Software_License.txt} +0 -0
- /cmdbox/licenses/{LICENSE_httplib2_0_22_0_MIT_License.txt → LICENSE_httplib2_0_31_0_MIT_License.txt} +0 -0
- /cmdbox/licenses/{LICENSE_huggingface-hub_0_34_4_Apache_Software_License.txt → LICENSE_huggingface-hub_0_35_3_Apache_Software_License.txt} +0 -0
- /cmdbox/licenses/{LICENSE_jaraco_functools_4_2_1_UNKNOWN.txt → LICENSE_jaraco_functools_4_3_0_UNKNOWN.txt} +0 -0
- /cmdbox/licenses/{LICENSE_jiter_0_10_0_MIT_License.txt → LICENSE_jiter_0_11_0_MIT_License.txt} +0 -0
- /cmdbox/licenses/{LICENSE_jsonschema-specifications_2025_4_1_UNKNOWN.txt → LICENSE_jsonschema-specifications_2025_9_1_UNKNOWN.txt} +0 -0
- /cmdbox/licenses/{LICENSE_jsonschema_4_25_0_UNKNOWN.txt → LICENSE_jsonschema_4_25_1_UNKNOWN.txt} +0 -0
- /cmdbox/licenses/{LICENSE_litellm_1_75_5_post1_MIT_License.txt → LICENSE_litellm_1_77_5_MIT_License.txt} +0 -0
- /cmdbox/licenses/{LICENSE_mcp_1_12_4_MIT_License.txt → LICENSE_mcp_1_16_0_MIT_License.txt} +0 -0
- /cmdbox/licenses/{LICENSE_more-itertools_10_7_0_MIT_License.txt → LICENSE_more-itertools_10_8_0_UNKNOWN.txt} +0 -0
- /cmdbox/licenses/{LICENSE_numpy_2_3_2_BSD_License.txt → LICENSE_numpy_2_3_3_BSD_License.txt} +0 -0
- /cmdbox/licenses/{LICENSE_openai_1_99_9_Apache_Software_License.txt → LICENSE_openai_2_1_0_Apache_Software_License.txt} +0 -0
- /cmdbox/licenses/{LICENSE_opentelemetry-api_1_36_0_UNKNOWN.txt → LICENSE_opentelemetry-api_1_37_0_UNKNOWN.txt} +0 -0
- /cmdbox/licenses/{LICENSE_opentelemetry-sdk_1_36_0_UNKNOWN.txt → LICENSE_opentelemetry-exporter-gcp-logging_1_9_0a0_Apache_Software_License.txt} +0 -0
- /cmdbox/licenses/{LICENSE_opentelemetry-semantic-conventions_0_57b0_UNKNOWN.txt → LICENSE_opentelemetry-exporter-gcp-monitoring_1_9_0a0_Apache_Software_License.txt} +0 -0
- /cmdbox/licenses/{LICENSE_prompt_toolkit_3_0_51_BSD_License.txt → LICENSE_prompt_toolkit_3_0_52_BSD_License.txt} +0 -0
- /cmdbox/licenses/{LICENSE_protobuf_6_31_1_3-Clause_BSD_License.txt → LICENSE_protobuf_6_32_1_3-Clause_BSD_License.txt} +0 -0
- /cmdbox/licenses/{LICENSE_psycopg-binary_3_2_9_GNU_Lesser_General_Public_License_v3-LGPLv3.txt → LICENSE_psycopg-binary_3_2_10_GNU_Lesser_General_Public_License_v3-LGPLv3.txt} +0 -0
- /cmdbox/licenses/{LICENSE_psycopg_3_2_9_GNU_Lesser_General_Public_License_v3-LGPLv3.txt → LICENSE_psycopg_3_2_10_GNU_Lesser_General_Public_License_v3-LGPLv3.txt} +0 -0
- /cmdbox/licenses/{LICENSE_pycparser_2_22_BSD_License.txt → LICENSE_pycparser_2_23_BSD_License.txt} +0 -0
- /cmdbox/licenses/{LICENSE_pydantic-settings_2_10_1_MIT_License.txt → LICENSE_pydantic-settings_2_11_0_MIT_License.txt} +0 -0
- /cmdbox/licenses/{LICENSE_pydantic_2_11_7_MIT_License.txt → LICENSE_pydantic_2_11_10_MIT_License.txt} +0 -0
- /cmdbox/licenses/{LICENSE_pyparsing_3_2_3_MIT_License.txt → LICENSE_pyparsing_3_2_5_UNKNOWN.txt} +0 -0
- /cmdbox/licenses/{LICENSE_pyperclip_1_9_0_BSD_License.txt → LICENSE_pyperclip_1_11_0_BSD_License.txt} +0 -0
- /cmdbox/licenses/{LICENSE_questionary_2_1_0_MIT_License.txt → LICENSE_questionary_2_1_1_MIT_License.txt} +0 -0
- /cmdbox/licenses/{LICENSE_regex_2025_7_34_UNKNOWN.txt → LICENSE_regex_2025_9_18_UNKNOWN.txt} +0 -0
- /cmdbox/licenses/{LICENSE_requests_2_32_4_Apache_Software_License.txt → LICENSE_requests_2_32_5_Apache_Software_License.txt} +0 -0
- /cmdbox/licenses/{LICENSE_rpds-py_0_27_0_UNKNOWN.txt → LICENSE_rpds-py_0_27_1_UNKNOWN.txt} +0 -0
- /cmdbox/licenses/{LICENSE_shapely_2_1_1_BSD_License.txt → LICENSE_shapely_2_1_2_BSD_License.txt} +0 -0
- /cmdbox/licenses/{LICENSE_sphinx-sitemap_2_7_2_UNKNOWN.txt → LICENSE_sphinx-sitemap_2_8_0_UNKNOWN.txt} +0 -0
- /cmdbox/licenses/{LICENSE_starlette_0_47_2_BSD_License.txt → LICENSE_starlette_0_48_0_BSD_License.txt} +0 -0
- /cmdbox/licenses/{LICENSE_tenacity_9_1_2_Apache_Software_License.txt → LICENSE_tenacity_8_5_0_Apache_Software_License.txt} +0 -0
- /cmdbox/licenses/{LICENSE_tokenizers_0_21_4_Apache_Software_License.txt → LICENSE_tokenizers_0_22_1_Apache_Software_License.txt} +0 -0
- /cmdbox/licenses/{LICENSE_twine_6_1_0_Apache_Software_License.txt → LICENSE_twine_6_2_0_UNKNOWN.txt} +0 -0
- /cmdbox/licenses/{LICENSE_typing-inspection_0_4_1_UNKNOWN.txt → LICENSE_typing-inspection_0_4_2_UNKNOWN.txt} +0 -0
- /cmdbox/licenses/{LICENSE_typing_extensions_4_14_1_UNKNOWN.txt → LICENSE_typing_extensions_4_15_0_UNKNOWN.txt} +0 -0
- /cmdbox/licenses/{LICENSE_wcwidth_0_2_13_MIT_License.txt → LICENSE_wcwidth_0_2_14_MIT_License.txt} +0 -0
- /cmdbox/licenses/{LICENSE_zope_event_5_1_1_Zope_Public_License.txt → LICENSE_zope_event_6_0_Zope_Public_License.txt} +0 -0
- /cmdbox/licenses/{LICENSE_zope_interface_7_2_Zope_Public_License.txt → LICENSE_zope_interface_8_0_1_Zope_Public_License.txt} +0 -0
- {cmdbox-0.6.4.2.dist-info → cmdbox-0.6.6.dist-info}/WHEEL +0 -0
- {cmdbox-0.6.4.2.dist-info → cmdbox-0.6.6.dist-info}/entry_points.txt +0 -0
- {cmdbox-0.6.4.2.dist-info → cmdbox-0.6.6.dist-info}/licenses/LICENSE +0 -0
- {cmdbox-0.6.4.2.dist-info → cmdbox-0.6.6.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
from cmdbox.app import common, client, filer, feature
|
|
2
|
+
from cmdbox.app.commons import convert
|
|
3
|
+
from cmdbox.app.options import Options
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Dict, Any, Tuple, List, Union
|
|
6
|
+
import argparse
|
|
7
|
+
import logging
|
|
8
|
+
import html
|
|
9
|
+
import re
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ExcelBase(feature.ResultEdgeFeature):
|
|
13
|
+
def get_mode(self) -> Union[str, List[str]]:
|
|
14
|
+
"""
|
|
15
|
+
この機能のモードを返します
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
Union[str, List[str]]: モード
|
|
19
|
+
"""
|
|
20
|
+
return 'excel'
|
|
21
|
+
|
|
22
|
+
def get_option(self):
|
|
23
|
+
"""
|
|
24
|
+
この機能のオプションを返します
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
Dict[str, Any]: オプション
|
|
28
|
+
"""
|
|
29
|
+
return dict(
|
|
30
|
+
use_redis=self.USE_REDIS_MEIGHT, nouse_webmode=False, use_agent=True,
|
|
31
|
+
description_ja="",
|
|
32
|
+
description_en="",
|
|
33
|
+
choice=[
|
|
34
|
+
dict(opt="host", type=Options.T_STR, default=self.default_host, required=True, multi=False, hide=True, choice=None, web="mask",
|
|
35
|
+
description_ja="Redisサーバーのサービスホストを指定します。",
|
|
36
|
+
description_en="Specify the service host of the Redis server."),
|
|
37
|
+
dict(opt="port", type=Options.T_INT, default=self.default_port, required=True, multi=False, hide=True, choice=None, web="mask",
|
|
38
|
+
description_ja="Redisサーバーのサービスポートを指定します。",
|
|
39
|
+
description_en="Specify the service port of the Redis server."),
|
|
40
|
+
dict(opt="password", type=Options.T_STR, default=self.default_pass, required=True, multi=False, hide=True, choice=None, web="mask",
|
|
41
|
+
description_ja="Redisサーバーのアクセスパスワード(任意)を指定します。省略時は `password` を使用します。",
|
|
42
|
+
description_en="Specify the access password of the Redis server (optional). If omitted, `password` is used."),
|
|
43
|
+
dict(opt="svname", type=Options.T_STR, default=self.default_svname, required=True, multi=False, hide=True, choice=None, web="readonly",
|
|
44
|
+
description_ja="サーバーのサービス名を指定します。省略時は `server` を使用します。",
|
|
45
|
+
description_en="Specify the service name of the inference server. If omitted, `server` is used."),
|
|
46
|
+
dict(opt="scope", type=Options.T_STR, default="client", required=True, multi=False, hide=False, choice=["client", "current", "server"],
|
|
47
|
+
description_ja="参照先スコープを指定します。指定可能な画像タイプは `client` , `current` , `server` です。",
|
|
48
|
+
description_en="Specifies the scope to be referenced. When omitted, 'client' is used.",
|
|
49
|
+
choice_show=dict(client=["client_data"]),
|
|
50
|
+
test_true={"server":"server",
|
|
51
|
+
"client":"client",
|
|
52
|
+
"current":"current"}),
|
|
53
|
+
dict(opt="svpath", type=Options.T_FILE, default="/", required=True, multi=False, hide=False, choice=None,
|
|
54
|
+
description_ja="サーバーのデータフォルダ以下のパスを指定します。省略時は `/` を使用します。",
|
|
55
|
+
description_en="Specify the directory path to get the list of files.",
|
|
56
|
+
test_true={"server":"/"}),
|
|
57
|
+
dict(opt="client_data", type=Options.T_STR, default=None, required=False, multi=False, hide=False, choice=None,
|
|
58
|
+
description_ja="ローカルを参照させる場合のデータフォルダのパスを指定します。",
|
|
59
|
+
description_en="Specify the path of the data folder when local is referenced.",
|
|
60
|
+
test_true={"server":None,
|
|
61
|
+
"client":common.HOME_DIR / f".{self.ver.__appid__}",
|
|
62
|
+
"current":None}),
|
|
63
|
+
dict(opt="retry_count", type=Options.T_INT, default=3, required=False, multi=False, hide=True, choice=None,
|
|
64
|
+
description_ja="Redisサーバーへの再接続回数を指定します。0以下を指定すると永遠に再接続を行います。",
|
|
65
|
+
description_en="Specifies the number of reconnections to the Redis server.If less than 0 is specified, reconnection is forever."),
|
|
66
|
+
dict(opt="retry_interval", type=Options.T_INT, default=5, required=False, multi=False, hide=True, choice=None,
|
|
67
|
+
description_ja="Redisサーバーに再接続までの秒数を指定します。",
|
|
68
|
+
description_en="Specifies the number of seconds before reconnecting to the Redis server."),
|
|
69
|
+
dict(opt="timeout", type=Options.T_INT, default="15", required=False, multi=False, hide=True, choice=None,
|
|
70
|
+
description_ja="サーバーの応答が返ってくるまでの最大待ち時間を指定。",
|
|
71
|
+
description_en="Specify the maximum waiting time until the server responds."),
|
|
72
|
+
dict(opt="output_json", short="o", type=Options.T_FILE, default=None, required=False, multi=False, hide=True, choice=None, fileio="out",
|
|
73
|
+
description_ja="処理結果jsonの保存先ファイルを指定。",
|
|
74
|
+
description_en="Specify the destination file for saving the processing result json."),
|
|
75
|
+
dict(opt="output_json_append", short="a", type=Options.T_BOOL, default=False, required=False, multi=False, hide=True, choice=[True, False],
|
|
76
|
+
description_ja="処理結果jsonファイルを追記保存します。",
|
|
77
|
+
description_en="Save the processing result json file by appending."),
|
|
78
|
+
dict(opt="stdout_log", type=Options.T_BOOL, default=True, required=False, multi=False, hide=True, choice=[True, False],
|
|
79
|
+
description_ja="GUIモードでのみ使用可能です。コマンド実行時の標準出力をConsole logに出力します。",
|
|
80
|
+
description_en="Available only in GUI mode. Outputs standard output during command execution to Console log."),
|
|
81
|
+
dict(opt="capture_stdout", type=Options.T_BOOL, default=True, required=False, multi=False, hide=True, choice=[True, False],
|
|
82
|
+
description_ja="GUIモードでのみ使用可能です。コマンド実行時の標準出力をキャプチャーし、実行結果画面に表示します。",
|
|
83
|
+
description_en="Available only in GUI mode. Captures standard output during command execution and displays it on the execution result screen."),
|
|
84
|
+
dict(opt="capture_maxsize", type=Options.T_INT, default=self.DEFAULT_CAPTURE_MAXSIZE, required=False, multi=False, hide=True, choice=None,
|
|
85
|
+
description_ja="GUIモードでのみ使用可能です。コマンド実行時の標準出力の最大キャプチャーサイズを指定します。",
|
|
86
|
+
description_en="Available only in GUI mode. Specifies the maximum capture size of standard output when executing commands."),
|
|
87
|
+
]
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
OPENPYXL_TYPE_TO_STRING = {
|
|
91
|
+
"n": "numeric",
|
|
92
|
+
"s": "string",
|
|
93
|
+
"f": "formula",
|
|
94
|
+
"b": "boolean",
|
|
95
|
+
"e": "error",
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
def apprun(self, logger:logging.Logger, args:argparse.Namespace, tm:float, pf:List[Dict[str, float]]=[]) -> Tuple[int, Dict[str, Any], Any]:
|
|
99
|
+
"""
|
|
100
|
+
この機能の実行を行います
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
logger (logging.Logger): ロガー
|
|
104
|
+
args (argparse.Namespace): 引数
|
|
105
|
+
tm (float): 実行開始時間
|
|
106
|
+
pf (List[Dict[str, float]]): 呼出元のパフォーマンス情報
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
Tuple[int, Dict[str, Any], Any]: 終了コード, 結果, オブジェクト
|
|
110
|
+
"""
|
|
111
|
+
chk, msg, _ = self.chk_args(args, tm, pf)
|
|
112
|
+
if chk != self.RESP_SUCCESS:
|
|
113
|
+
common.print_format(msg, args.format, tm, args.output_json, args.output_json_append, pf=pf)
|
|
114
|
+
return self.RESP_WARN, msg, None
|
|
115
|
+
|
|
116
|
+
try:
|
|
117
|
+
client_data = Path(args.client_data.replace('"','')) if args.client_data is not None else None
|
|
118
|
+
if args.scope == "client":
|
|
119
|
+
if client_data is not None:
|
|
120
|
+
f = filer.Filer(client_data, logger)
|
|
121
|
+
chk, abspath, msg = f._file_exists(args.svpath)
|
|
122
|
+
if not chk:
|
|
123
|
+
common.print_format(msg, args.format, tm, args.output_json, args.output_json_append, pf=pf)
|
|
124
|
+
return self.RESP_WARN, msg, None
|
|
125
|
+
res = self.excel_proc(abspath, args, logger, tm, pf)
|
|
126
|
+
if 'success' not in res:
|
|
127
|
+
common.print_format(res, args.format, tm, args.output_json, args.output_json_append, pf=pf)
|
|
128
|
+
return self.RESP_WARN, res, None
|
|
129
|
+
common.print_format(res, args.format, tm, args.output_json, args.output_json_append, pf=pf)
|
|
130
|
+
return self.RESP_SUCCESS, res, None
|
|
131
|
+
else:
|
|
132
|
+
msg = dict(warn=f"client_data is empty.")
|
|
133
|
+
common.print_format(msg, args.format, tm, args.output_json, args.output_json_append, pf=pf)
|
|
134
|
+
return self.RESP_WARN, msg, None
|
|
135
|
+
elif args.scope == "current":
|
|
136
|
+
f = filer.Filer(Path.cwd(), logger)
|
|
137
|
+
chk, abspath, msg = f._file_exists(args.svpath)
|
|
138
|
+
if not chk:
|
|
139
|
+
common.print_format(msg, args.format, tm, args.output_json, args.output_json_append, pf=pf)
|
|
140
|
+
return self.RESP_WARN, msg, None
|
|
141
|
+
res = self.excel_proc(abspath, args, logger, tm, pf)
|
|
142
|
+
if 'success' not in res:
|
|
143
|
+
common.print_format(res, args.format, tm, args.output_json, args.output_json_append, pf=pf)
|
|
144
|
+
return self.RESP_WARN, res, None
|
|
145
|
+
common.print_format(res, args.format, tm, args.output_json, args.output_json_append, pf=pf)
|
|
146
|
+
return self.RESP_SUCCESS, res, None
|
|
147
|
+
elif args.scope == "server":
|
|
148
|
+
cl = client.Client(logger, redis_host=args.host, redis_port=args.port, redis_password=args.password, svname=args.svname)
|
|
149
|
+
res = cl.redis_cli.send_cmd(self.get_svcmd(), self.get_svparam(args),
|
|
150
|
+
retry_count=args.retry_count, retry_interval=args.retry_interval, timeout=args.timeout)
|
|
151
|
+
if 'success' not in res:
|
|
152
|
+
common.print_format(res, args.format, tm, args.output_json, args.output_json_append, pf=pf)
|
|
153
|
+
return self.RESP_WARN, res, None
|
|
154
|
+
common.print_format(res, args.format, tm, args.output_json, args.output_json_append, pf=pf)
|
|
155
|
+
return self.RESP_SUCCESS, res, None
|
|
156
|
+
else:
|
|
157
|
+
logger.warning(f"scope is invalid. {args.scope}")
|
|
158
|
+
return dict(warn=f"scope is invalid. {args.scope}")
|
|
159
|
+
except Exception as e:
|
|
160
|
+
logger.warning(f"Exception occurred. {e}", exc_info=True)
|
|
161
|
+
return self.RESP_WARN, dict(warn=f"Exception occurred. {e}"), None
|
|
162
|
+
|
|
163
|
+
def chk_args(self, args:argparse.Namespace, tm:float, pf:List[Dict[str, float]]=[]) -> Tuple[bool, str, Any]:
|
|
164
|
+
"""
|
|
165
|
+
引数のチェックを行います
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
args (argparse.Namespace): 引数
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
Tuple[bool, str]: チェック結果, メッセージ
|
|
172
|
+
"""
|
|
173
|
+
if args.svname is None:
|
|
174
|
+
msg = dict(warn=f"Please specify the --svname option.")
|
|
175
|
+
common.print_format(msg, args.format, tm, args.output_json, args.output_json_append, pf=pf)
|
|
176
|
+
return self.RESP_WARN, msg, None
|
|
177
|
+
if args.scope is None:
|
|
178
|
+
msg = dict(warn=f"Please specify the --scope option.")
|
|
179
|
+
common.print_format(msg, args.format, tm, args.output_json, args.output_json_append, pf=pf)
|
|
180
|
+
return self.RESP_WARN, msg, None
|
|
181
|
+
if args.svpath is None:
|
|
182
|
+
msg = dict(warn=f"Please specify the --svpath option.")
|
|
183
|
+
common.print_format(msg, args.format, tm, args.output_json, args.output_json_append, pf=pf)
|
|
184
|
+
return self.RESP_WARN, msg, None
|
|
185
|
+
return self.RESP_SUCCESS, None, None
|
|
186
|
+
|
|
187
|
+
def excel_proc(self, abspath:Path, args:argparse.Namespace, logger:logging.Logger, tm:float, pf:List[Dict[str, float]]=[]) -> Dict[str, Any]:
|
|
188
|
+
"""
|
|
189
|
+
Excel処理のベース
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
abspath (Path): Excelファイルの絶対パス
|
|
193
|
+
args (argparse.Namespace): 引数
|
|
194
|
+
logger (logging.Logger): ロガー
|
|
195
|
+
tm (float): 処理時間
|
|
196
|
+
pf (List[Dict[str, float]]): パフォーマンス情報
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
Dict[str, Any]: 結果
|
|
200
|
+
"""
|
|
201
|
+
raise NotImplementedError("Excel processing is not implemented.")
|
|
202
|
+
|
|
203
|
+
def get_svparam(self, args:argparse.Namespace) -> List[str]:
|
|
204
|
+
"""
|
|
205
|
+
サーバーに送信するパラメーターを返します
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
args (argparse.Namespace): 引数
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
List[str]: サーバーに送信するパラメーター
|
|
212
|
+
"""
|
|
213
|
+
raise NotImplementedError("Get svparam is not implemented.")
|
|
214
|
+
|
|
215
|
+
def format_cell(self, output_cell_format:str, otxt:str, val:str, logger:logging.Logger) -> str:
|
|
216
|
+
"""
|
|
217
|
+
テキストをフォーマットに応じて、valをフォーマットします
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
output_cell_format (str): 出力フォーマット
|
|
221
|
+
otxt (str): 追加先のテキスト
|
|
222
|
+
val (str): セルの値
|
|
223
|
+
logger (logging.Logger): ロガー
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
str: 追加後のテキスト
|
|
227
|
+
"""
|
|
228
|
+
val = str(val) if val is not None else ""
|
|
229
|
+
otxt = otxt if otxt is not None else ""
|
|
230
|
+
ret = ""
|
|
231
|
+
if output_cell_format == 'csv':
|
|
232
|
+
ret = val.replace("\n", " ").replace("\r", "")
|
|
233
|
+
ret = ret.replace('"', '""')
|
|
234
|
+
ret = ret if ret.find(",")>-1 else f'"{ret}"'
|
|
235
|
+
if otxt and not otxt.endswith("\n"):
|
|
236
|
+
ret = f",{ret}"
|
|
237
|
+
elif output_cell_format == 'md':
|
|
238
|
+
ret = val.replace("\n", " ").replace("\r", "")
|
|
239
|
+
ret = ret.replace('|', r'\|')
|
|
240
|
+
if otxt and not otxt.endswith("\n"):
|
|
241
|
+
ret = f"{ret}|"
|
|
242
|
+
else:
|
|
243
|
+
ret = f"|{ret}|"
|
|
244
|
+
elif output_cell_format == 'html':
|
|
245
|
+
ret = html.escape(val)
|
|
246
|
+
ret = ret.replace("\n", "<br/>").replace("\r", "")
|
|
247
|
+
ret = f"<td>{ret}</td>"
|
|
248
|
+
return ret
|
|
249
|
+
|
|
250
|
+
def format_newline(self, output_cell_format:str, otxt:str, logger:logging.Logger) -> str:
|
|
251
|
+
"""
|
|
252
|
+
テキストをフォーマットに応じて改行を追加します
|
|
253
|
+
|
|
254
|
+
Args:
|
|
255
|
+
output_cell_format (str): 出力フォーマット
|
|
256
|
+
otxt (str): 追加先のテキスト
|
|
257
|
+
logger (logging.Logger): ロガー
|
|
258
|
+
|
|
259
|
+
Returns:
|
|
260
|
+
str: 追加後のテキスト
|
|
261
|
+
"""
|
|
262
|
+
otxt = otxt if otxt is not None else ""
|
|
263
|
+
ret = ""
|
|
264
|
+
if output_cell_format == 'csv':
|
|
265
|
+
ret = f"{otxt}\n" if otxt else "\n"
|
|
266
|
+
elif output_cell_format == 'md':
|
|
267
|
+
ret = f"{otxt}\n" if otxt else "\n"
|
|
268
|
+
elif output_cell_format == 'html':
|
|
269
|
+
if otxt and otxt.find(r"<TTRR/>") > -1:
|
|
270
|
+
pre = re.sub(r'<TTRR/>.*', '', otxt, flags=re.DOTALL)
|
|
271
|
+
post = re.sub(r'.+<TTRR/>', '', otxt, flags=re.DOTALL)
|
|
272
|
+
ret = f"{pre}<tr>{post}</tr>\n<TTRR/>"
|
|
273
|
+
elif otxt:
|
|
274
|
+
ret = f"<tr>{otxt}</tr>\n<TTRR/>"
|
|
275
|
+
elif not otxt:
|
|
276
|
+
ret = "<tr></tr>\n<TTRR/>"
|
|
277
|
+
return ret
|
|
278
|
+
|
|
279
|
+
def format_table(self, output_cell_format:str, otxt:str, logger:logging.Logger) -> str:
|
|
280
|
+
"""
|
|
281
|
+
テキストをフォーマットに応じてテーブルを構成します
|
|
282
|
+
Args:
|
|
283
|
+
output_cell_format (str): 出力フォーマット
|
|
284
|
+
otxt (str): 追加先のテキスト
|
|
285
|
+
logger (logging.Logger): ロガー
|
|
286
|
+
Returns:
|
|
287
|
+
str: 追加後のテキスト
|
|
288
|
+
"""
|
|
289
|
+
otxt = otxt if otxt is not None else ""
|
|
290
|
+
ret = ""
|
|
291
|
+
if output_cell_format == 'csv':
|
|
292
|
+
ret = f"{otxt}\n" if otxt else "\n"
|
|
293
|
+
elif output_cell_format == 'md':
|
|
294
|
+
ret = f"{otxt}\n" if otxt else "\n"
|
|
295
|
+
elif output_cell_format == 'html':
|
|
296
|
+
otxt = re.sub(r'<TTRR/>.*', '', otxt, flags=re.DOTALL)
|
|
297
|
+
if otxt:
|
|
298
|
+
ret = f"<table>{otxt}</table>\n"
|
|
299
|
+
elif not otxt:
|
|
300
|
+
ret = "\n"
|
|
301
|
+
return ret
|
|
@@ -34,16 +34,14 @@ class ExecCmd(cmdbox_web_load_cmd.LoadCmd):
|
|
|
34
34
|
raise HTTPException(status_code=401, detail=self.DEFAULT_401_MESSAGE)
|
|
35
35
|
opt = None
|
|
36
36
|
content_type = req.headers.get('content-type')
|
|
37
|
-
def _marge_opt(opt, param):
|
|
38
|
-
|
|
39
|
-
if k in param: opt[k] = param[k]
|
|
37
|
+
def _marge_opt(opt:Dict[str, Any], param:Dict[str, Any]) -> Dict[str, Any]:
|
|
38
|
+
opt.update(param)
|
|
40
39
|
return opt
|
|
40
|
+
opt_def = self.load_cmd(web, title)
|
|
41
41
|
if content_type is None:
|
|
42
|
-
opt =
|
|
43
|
-
opt = _marge_opt(opt, req.query_params)
|
|
42
|
+
opt = _marge_opt(opt_def, req.query_params)
|
|
44
43
|
elif content_type.startswith('multipart/form-data'):
|
|
45
|
-
opt =
|
|
46
|
-
opt = _marge_opt(opt, req.query_params)
|
|
44
|
+
opt = _marge_opt(opt_def, req.query_params)
|
|
47
45
|
form = await req.form()
|
|
48
46
|
#files = {key: value for key, value in form.multi_items() if isinstance(value, UploadFile)}
|
|
49
47
|
for key, fv in form.multi_items():
|
|
@@ -52,13 +50,12 @@ class ExecCmd(cmdbox_web_load_cmd.LoadCmd):
|
|
|
52
50
|
if key == 'input_file': opt['stdin'] = False
|
|
53
51
|
elif content_type.startswith('application/json'):
|
|
54
52
|
opt = await req.json()
|
|
53
|
+
opt = _marge_opt(opt_def, opt)
|
|
55
54
|
elif content_type.startswith('application/octet-stream'):
|
|
56
|
-
opt =
|
|
57
|
-
opt = _marge_opt(opt, req.query_params)
|
|
55
|
+
opt = _marge_opt(opt_def, req.query_params)
|
|
58
56
|
opt['_stdin_body'] = await req.body()
|
|
59
57
|
else:
|
|
60
|
-
opt =
|
|
61
|
-
opt = _marge_opt(opt, req.query_params)
|
|
58
|
+
opt = _marge_opt(opt_def, req.query_params)
|
|
62
59
|
if 'mode' not in opt or 'cmd' not in opt:
|
|
63
60
|
raise HTTPException(status_code=404, detail='mode or cmd is not found.')
|
|
64
61
|
opt['capture_stdout'] = nothread = True
|
|
@@ -147,9 +144,10 @@ class ExecCmd(cmdbox_web_load_cmd.LoadCmd):
|
|
|
147
144
|
if 'port' in opt: opt['port'] = web.redis_port
|
|
148
145
|
if 'password' in opt: opt['password'] = web.redis_password
|
|
149
146
|
if 'svname' in opt: opt['svname'] = web.svname
|
|
150
|
-
if
|
|
151
|
-
if '
|
|
152
|
-
|
|
147
|
+
if not 'clmsg_id' in opt: # optに含まれる場合は処理しない
|
|
148
|
+
if req.session is not None and 'signin' in req.session and req.session['signin'] is not None:
|
|
149
|
+
if 'clmsg_id' in req.session['signin'] and req.session['signin']['clmsg_id'] is not None:
|
|
150
|
+
opt['clmsg_id'] = req.session['signin']['clmsg_id']
|
|
153
151
|
ap.sv = None
|
|
154
152
|
ap.cl = None
|
|
155
153
|
ap.web = None
|
cmdbox/app/filer.py
CHANGED
|
@@ -206,13 +206,15 @@ class Filer(object):
|
|
|
206
206
|
try:
|
|
207
207
|
mime_type, encoding = mimetypes.guess_type(str(abspath))
|
|
208
208
|
fname = abspath.name
|
|
209
|
-
|
|
209
|
+
def _r(f):
|
|
210
210
|
fd = f.read()
|
|
211
211
|
if mime_type is not None and mime_type != 'image/svg+xml' and mime_type.startswith('image') and img_thumbnail > 0:
|
|
212
212
|
img = convert.imgbytes2thumbnail(fd, (img_thumbnail, img_thumbnail))
|
|
213
213
|
fd = convert.img2byte(img, "jpeg")
|
|
214
214
|
fname = f"{fname}.thumbnail.jpg"
|
|
215
215
|
data = convert.bytes2b64str(fd)
|
|
216
|
+
return data
|
|
217
|
+
data = common.load_file(abspath, _r, mode='rb')
|
|
216
218
|
return self.RESP_SUCCESS, dict(success=dict(name=fname, data=data, mime_type=mime_type))
|
|
217
219
|
except Exception as e:
|
|
218
220
|
self.logger.warning(f"Failed to download {abspath}. {e}")
|
|
@@ -252,8 +254,9 @@ class Filer(object):
|
|
|
252
254
|
try:
|
|
253
255
|
if mkdir:
|
|
254
256
|
save_path.parent.mkdir(parents=True, exist_ok=True)
|
|
255
|
-
|
|
257
|
+
def _w(f):
|
|
256
258
|
f.write(file_data)
|
|
259
|
+
common.save_file(Path(save_path), _w, mode='wb')
|
|
257
260
|
return self.RESP_SUCCESS, dict(success=f"Uploaded {save_path}")
|
|
258
261
|
except Exception as e:
|
|
259
262
|
self.logger.warning(f"Failed to upload {save_path}. {e}")
|
cmdbox/app/mcp.py
CHANGED
|
@@ -186,8 +186,9 @@ class Mcp:
|
|
|
186
186
|
if args.llmmodel is None: raise ValueError("llmmodel is required.")
|
|
187
187
|
if args.llmlocation is None: raise ValueError("llmlocation is required.")
|
|
188
188
|
if args.llmsvaccountfile is not None:
|
|
189
|
-
|
|
190
|
-
|
|
189
|
+
def _r(f):
|
|
190
|
+
return json.load(f)
|
|
191
|
+
vertex_credentials = common.load_file(Path(args.llmsvaccountfile), _r, mode='r')
|
|
191
192
|
elif args.llmprojectid is None: raise ValueError("llmprojectid is required.")
|
|
192
193
|
agent = Agent(
|
|
193
194
|
name=args.agent_name,
|
|
@@ -353,7 +354,7 @@ class Mcp:
|
|
|
353
354
|
func_txt += f' signin_data = signin.Signin.load_signin_file(args.signin_file)\n'
|
|
354
355
|
func_txt += f' req = scope["req"] if scope["req"] is not None else scope["websocket"]\n'
|
|
355
356
|
func_txt += f' sign = signin.Signin._check_signin(req, scope["res"], signin_data, logger)\n'
|
|
356
|
-
func_txt += f' if sign is not None:\n'
|
|
357
|
+
func_txt += f' if sign is not None or "signin" not in req.session or "groups" not in req.session["signin"]:\n'
|
|
357
358
|
func_txt += f' logger.warning("Unable to execute command because authentication information cannot be obtained")\n'
|
|
358
359
|
func_txt += f' return dict(warn="Unable to execute command because authentication information cannot be obtained")\n'
|
|
359
360
|
func_txt += f' groups = req.session["signin"]["groups"]\n'
|
cmdbox/app/options.py
CHANGED
|
@@ -267,6 +267,14 @@ class Options:
|
|
|
267
267
|
short="d", type=Options.T_BOOL, default=False, required=False, multi=False, hide=True, choice=[True, False],
|
|
268
268
|
description_ja="デバックモードで起動します。",
|
|
269
269
|
description_en="Starts in debug mode.")
|
|
270
|
+
self._options["debug_attach"] = dict(
|
|
271
|
+
short="debug_attach", type=Options.T_BOOL, default=False, required=False, multi=False, hide=True, choice=[True, False],
|
|
272
|
+
description_ja="デバックプロセスへのアタッチを有効にするかどうかを指定します。",
|
|
273
|
+
description_en="Specify whether to enable attaching to the debug process.")
|
|
274
|
+
self._options["debug_attach_port"] = dict(
|
|
275
|
+
short="debug_attach_port", type=Options.T_INT, default=5678, required=False, multi=False, hide=True, choice=None,
|
|
276
|
+
description_ja="デバックプロセスにアタッチするポート番号を指定します。",
|
|
277
|
+
description_en="Specify the port number to attach to the debug process.")
|
|
270
278
|
self._options["format"] = dict(
|
|
271
279
|
short="f", type=Options.T_BOOL, default=None, required=False, multi=False, hide=True,
|
|
272
280
|
description_ja="処理結果を見やすい形式で出力します。指定しない場合json形式で出力します。",
|
cmdbox/app/web.py
CHANGED
|
@@ -719,7 +719,7 @@ class Web:
|
|
|
719
719
|
def start(self, allow_host:str="0.0.0.0", listen_port:int=8081, ssl_listen_port:int=8443,
|
|
720
720
|
ssl_cert:Path=None, ssl_key:Path=None, ssl_keypass:str=None, ssl_ca_certs:Path=None,
|
|
721
721
|
session_domain:str=None, session_path:str='/', session_secure:bool=False, session_timeout:int=900, outputs_key:List[str]=[],
|
|
722
|
-
|
|
722
|
+
gunicorn_workers:int=-1, gunicorn_timeout:int=30,
|
|
723
723
|
agent_runner=None, mcp=None,):
|
|
724
724
|
"""
|
|
725
725
|
Webサーバを起動する
|
|
@@ -737,8 +737,8 @@ class Web:
|
|
|
737
737
|
session_secure (bool, optional): セッションセキュア. Defaults to False.
|
|
738
738
|
session_timeout (int, optional): セッションタイムアウト. Defaults to 900.
|
|
739
739
|
outputs_key (list, optional): 出力キー. Defaults to [].
|
|
740
|
-
|
|
741
|
-
|
|
740
|
+
gunicorn_workers (int, optional): Gunicornワーカー数. Defaults to -1.
|
|
741
|
+
gunicorn_timeout (int, optional): Gunicornタイムアウト. Defaults to 30.
|
|
742
742
|
agent_runner (Runner, optional): エージェントランナー. Defaults to None.
|
|
743
743
|
mcp (MCP, optional): MCP. Defaults to None.
|
|
744
744
|
"""
|
|
@@ -754,8 +754,8 @@ class Web:
|
|
|
754
754
|
self.session_path = session_path
|
|
755
755
|
self.session_secure = session_secure
|
|
756
756
|
self.session_timeout = session_timeout
|
|
757
|
-
self.
|
|
758
|
-
self.
|
|
757
|
+
self.gunicorn_workers = gunicorn_workers
|
|
758
|
+
self.gunicorn_timeout = gunicorn_timeout
|
|
759
759
|
self.agent_runner = agent_runner
|
|
760
760
|
self.mcp = mcp
|
|
761
761
|
if self.logger.level == logging.DEBUG:
|
|
@@ -771,8 +771,8 @@ class Web:
|
|
|
771
771
|
self.logger.debug(f"web start parameter: session_path={self.session_path}")
|
|
772
772
|
self.logger.debug(f"web start parameter: session_secure={self.session_secure}")
|
|
773
773
|
self.logger.debug(f"web start parameter: session_timeout={self.session_timeout}")
|
|
774
|
-
self.logger.debug(f"web start parameter:
|
|
775
|
-
self.logger.debug(f"web start parameter:
|
|
774
|
+
self.logger.debug(f"web start parameter: gunicorn_workers={self.gunicorn_workers}")
|
|
775
|
+
self.logger.debug(f"web start parameter: gunicorn_timeout={self.gunicorn_timeout}")
|
|
776
776
|
self.logger.debug(f"web start parameter: agent_runner={self.agent_runner}")
|
|
777
777
|
self.logger.debug(f"web start parameter: mcp={self.mcp}")
|
|
778
778
|
|
|
@@ -894,21 +894,22 @@ class Web:
|
|
|
894
894
|
https_config = Config(app=app, host=self.allow_host, port=self.ssl_listen_port,
|
|
895
895
|
ssl_certfile=self.ssl_cert, ssl_keyfile=self.ssl_key,
|
|
896
896
|
ssl_keyfile_password=self.ssl_keypass, ssl_ca_certs=self.ssl_ca_certs)
|
|
897
|
-
th_ssl =
|
|
898
|
-
|
|
897
|
+
th_ssl = ThreadedASGI(app, self.logger, config=https_config,
|
|
898
|
+
gunicorn_config=dict(workers=self.gunicorn_workers, timeout=self.gunicorn_timeout))
|
|
899
899
|
th_ssl.start()
|
|
900
900
|
browser_port = self.ssl_listen_port
|
|
901
901
|
else:
|
|
902
902
|
http_config = Config(app=app, host=self.allow_host, port=self.listen_port)
|
|
903
|
-
th =
|
|
904
|
-
|
|
903
|
+
th = ThreadedASGI(app, self.logger, config=http_config,
|
|
904
|
+
gunicorn_config=dict(workers=self.gunicorn_workers, timeout=self.gunicorn_timeout))
|
|
905
905
|
th.start()
|
|
906
906
|
browser_port = self.listen_port
|
|
907
907
|
try:
|
|
908
908
|
if self.gui_mode:
|
|
909
909
|
webbrowser.open(f'http://localhost:{browser_port}/gui')
|
|
910
|
-
|
|
910
|
+
def _w(f):
|
|
911
911
|
f.write(str(os.getpid()))
|
|
912
|
+
common.save_file("web.pid", _w)
|
|
912
913
|
while self.is_running:
|
|
913
914
|
gevent.sleep(1)
|
|
914
915
|
if th is not None:
|
|
@@ -926,35 +927,42 @@ class Web:
|
|
|
926
927
|
Webサーバを停止する
|
|
927
928
|
"""
|
|
928
929
|
try:
|
|
929
|
-
|
|
930
|
+
def _r(f):
|
|
930
931
|
pid = f.read()
|
|
931
932
|
if pid != "":
|
|
932
|
-
|
|
933
|
+
if platform.system() == "Windows":
|
|
934
|
+
os.system(f"taskkill /F /PID {pid}")
|
|
935
|
+
else:
|
|
936
|
+
os.kill(int(pid), signal.SIGKILL)
|
|
933
937
|
self.logger.info(f"Stop web.")
|
|
934
938
|
else:
|
|
935
939
|
self.logger.warning(f"pid is empty.")
|
|
940
|
+
common.load_file("web.pid", _r)
|
|
936
941
|
Path("web.pid").unlink(missing_ok=True)
|
|
937
942
|
except:
|
|
938
943
|
traceback.print_exc()
|
|
939
944
|
finally:
|
|
940
945
|
self.logger.info(f"Exit web.")
|
|
941
946
|
|
|
942
|
-
class
|
|
943
|
-
def __init__(self, logger:logging.Logger, config:Config,
|
|
947
|
+
class ThreadedASGI:
|
|
948
|
+
def __init__(self, app:FastAPI, logger:logging.Logger, config:Config, gunicorn_config:Dict[str, Any]=None, force_single:bool=False):
|
|
949
|
+
self.app = app
|
|
944
950
|
self.logger = logger
|
|
945
|
-
self.
|
|
946
|
-
self.
|
|
951
|
+
self.config = config
|
|
952
|
+
self.gunicorn_config = gunicorn_config
|
|
953
|
+
# windows環境下ではシングルプロセスで動作させる
|
|
954
|
+
self.force_single = True if platform.system() == "Windows" else force_single
|
|
947
955
|
# loggerの設定
|
|
948
956
|
common.reset_logger("uvicorn")
|
|
949
957
|
common.reset_logger("uvicorn.error")
|
|
950
958
|
common.reset_logger("uvicorn.access")
|
|
951
959
|
#common.reset_logger("gunicorn.error")
|
|
952
960
|
#common.reset_logger("gunicorn.access")
|
|
953
|
-
if self.
|
|
961
|
+
if self.force_single:
|
|
962
|
+
config.ws = "wsproto"
|
|
954
963
|
self.server = uvicorn.Server(config)
|
|
955
964
|
self.thread = RaiseThread(daemon=True, target=self.server.run)
|
|
956
965
|
else:
|
|
957
|
-
|
|
958
966
|
from gunicorn.app.wsgiapp import WSGIApplication
|
|
959
967
|
class App(WSGIApplication):
|
|
960
968
|
def __init__(self, app, options):
|
|
@@ -969,32 +977,36 @@ class ThreadedUvicorn:
|
|
|
969
977
|
def load(self):
|
|
970
978
|
return self.application
|
|
971
979
|
opt = dict(bind=f"{config.host}:{config.port}",
|
|
972
|
-
worker_class="
|
|
980
|
+
worker_class="cmdbox.app.web.ASGIWorker",
|
|
973
981
|
access_log_format='[%(t)s] %(p)s %(l)s %(h)s "%(r)s" %(s)s',
|
|
974
982
|
loglevel=logging.getLevelName(self.logger.level),
|
|
975
983
|
keyfile=config.ssl_keyfile, certfile=config.ssl_certfile,
|
|
976
984
|
ca_certs=config.ssl_ca_certs, keyfile_password=config.ssl_keyfile_password,
|
|
977
985
|
limit_request_line=8190, limit_request_fields=100, limit_request_field_size=8190)
|
|
978
986
|
|
|
979
|
-
self.
|
|
980
|
-
if 'workers' not in self.
|
|
981
|
-
self.
|
|
982
|
-
if self.
|
|
983
|
-
self.
|
|
984
|
-
if 'timeout' not in self.
|
|
985
|
-
self.
|
|
986
|
-
if self.
|
|
987
|
-
self.
|
|
987
|
+
self.gunicorn_config = self.gunicorn_config or {}
|
|
988
|
+
if 'workers' not in self.gunicorn_config:
|
|
989
|
+
self.gunicorn_config['workers'] = None
|
|
990
|
+
if self.gunicorn_config['workers'] is None or self.gunicorn_config['workers'] <= 0:
|
|
991
|
+
self.gunicorn_config['workers'] = multiprocessing.cpu_count()*2
|
|
992
|
+
if 'timeout' not in self.gunicorn_config:
|
|
993
|
+
self.gunicorn_config['timeout'] = None
|
|
994
|
+
if self.gunicorn_config['timeout'] is None or self.gunicorn_config['timeout'] <= 0:
|
|
995
|
+
self.gunicorn_config['timeout'] = 30
|
|
988
996
|
|
|
989
|
-
opt = {**opt, **self.
|
|
990
|
-
self.server = App(
|
|
991
|
-
#self.thread = RaiseThread(daemon=True, target=self.server.run)
|
|
997
|
+
opt = {**opt, **self.gunicorn_config}
|
|
998
|
+
self.server = App(app, opt)
|
|
992
999
|
|
|
993
1000
|
def start(self):
|
|
994
|
-
if self.
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
1001
|
+
if self.force_single:
|
|
1002
|
+
if platform.system() == "Windows":
|
|
1003
|
+
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
|
1004
|
+
self.thread.start()
|
|
1005
|
+
asyncio.run(self.wait_for_started())
|
|
1006
|
+
else:
|
|
1007
|
+
self.thread.start()
|
|
1008
|
+
task = asyncio.get_event_loop().create_task(self.wait_for_started())
|
|
1009
|
+
# task.result()
|
|
998
1010
|
else:
|
|
999
1011
|
async def run():
|
|
1000
1012
|
self.server.run()
|
|
@@ -1005,7 +1017,7 @@ class ThreadedUvicorn:
|
|
|
1005
1017
|
await asyncio.sleep(0.1)
|
|
1006
1018
|
|
|
1007
1019
|
def stop(self):
|
|
1008
|
-
if self.
|
|
1020
|
+
if self.force_single:
|
|
1009
1021
|
if self.thread.is_alive():
|
|
1010
1022
|
self.server.should_exit = True
|
|
1011
1023
|
self.thread.raise_exception()
|
|
@@ -1015,7 +1027,7 @@ class ThreadedUvicorn:
|
|
|
1015
1027
|
self.server.started = False
|
|
1016
1028
|
|
|
1017
1029
|
def is_alive(self):
|
|
1018
|
-
if self.
|
|
1030
|
+
if self.force_single:
|
|
1019
1031
|
return self.thread.is_alive()
|
|
1020
1032
|
else:
|
|
1021
1033
|
return self.server.started
|
|
@@ -1044,3 +1056,10 @@ class RaiseThread(threading.Thread):
|
|
|
1044
1056
|
0
|
|
1045
1057
|
)
|
|
1046
1058
|
print('Failure in raising exception')
|
|
1059
|
+
|
|
1060
|
+
if platform.system() != "Windows":
|
|
1061
|
+
from uvicorn.workers import UvicornWorker
|
|
1062
|
+
class ASGIWorker(UvicornWorker):
|
|
1063
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
1064
|
+
super().__init__(*args, **kwargs)
|
|
1065
|
+
self.config.ws = "wsproto"
|
cmdbox/extensions/features.yml
CHANGED
|
@@ -46,6 +46,9 @@ agentrule: # Specifies a list of rules that determi
|
|
|
46
46
|
- mode: client
|
|
47
47
|
cmds: [file_download, file_list, http, server_info]
|
|
48
48
|
rule: allow
|
|
49
|
+
- mode: excel
|
|
50
|
+
cmds: [cell_details, cell_search, cell_values, sheet_list]
|
|
51
|
+
rule: allow
|
|
49
52
|
- mode: server
|
|
50
53
|
cmds: [list]
|
|
51
54
|
rule: allow
|