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.

Files changed (140) hide show
  1. cmdbox/app/app.py +7 -0
  2. cmdbox/app/client.py +384 -383
  3. cmdbox/app/common.py +85 -7
  4. cmdbox/app/commons/convert.py +3 -1
  5. cmdbox/app/edge.py +12 -12
  6. cmdbox/app/feature.py +1 -1
  7. cmdbox/app/features/cli/{cmdbox_vision_install.py → _cmdbox_vision_install.py} +2 -10
  8. cmdbox/app/features/cli/_cmdbox_vision_predict.py +487 -0
  9. cmdbox/app/features/cli/{cmdbox_vision_start.py → _cmdbox_vision_start.py} +5 -1
  10. cmdbox/app/features/cli/cmdbox_audit_createdb.py +1 -11
  11. cmdbox/app/features/cli/cmdbox_audit_delete.py +0 -9
  12. cmdbox/app/features/cli/cmdbox_audit_search.py +0 -9
  13. cmdbox/app/features/cli/cmdbox_audit_write.py +0 -9
  14. cmdbox/app/features/cli/cmdbox_cmd_list.py +3 -3
  15. cmdbox/app/features/cli/cmdbox_cmd_load.py +3 -3
  16. cmdbox/app/features/cli/cmdbox_excel_cell_details.py +436 -0
  17. cmdbox/app/features/cli/cmdbox_excel_cell_search.py +276 -0
  18. cmdbox/app/features/cli/cmdbox_excel_cell_values.py +258 -0
  19. cmdbox/app/features/cli/cmdbox_excel_sheet_list.py +159 -0
  20. cmdbox/app/features/cli/cmdbox_tts_install.py +4 -11
  21. cmdbox/app/features/cli/cmdbox_tts_say.py +2 -10
  22. cmdbox/app/features/cli/cmdbox_tts_start.py +0 -9
  23. cmdbox/app/features/cli/cmdbox_tts_stop.py +0 -9
  24. cmdbox/app/features/cli/cmdbox_web_apikey_add.py +3 -3
  25. cmdbox/app/features/cli/cmdbox_web_apikey_del.py +3 -3
  26. cmdbox/app/features/cli/cmdbox_web_group_add.py +3 -3
  27. cmdbox/app/features/cli/cmdbox_web_group_del.py +3 -3
  28. cmdbox/app/features/cli/cmdbox_web_group_edit.py +3 -3
  29. cmdbox/app/features/cli/cmdbox_web_group_list.py +3 -3
  30. cmdbox/app/features/cli/cmdbox_web_start.py +10 -10
  31. cmdbox/app/features/cli/cmdbox_web_user_add.py +3 -3
  32. cmdbox/app/features/cli/cmdbox_web_user_del.py +3 -3
  33. cmdbox/app/features/cli/cmdbox_web_user_edit.py +3 -3
  34. cmdbox/app/features/cli/cmdbox_web_user_list.py +3 -3
  35. cmdbox/app/features/cli/excel_base.py +301 -0
  36. cmdbox/app/features/web/cmdbox_web_exec_cmd.py +12 -14
  37. cmdbox/app/filer.py +5 -2
  38. cmdbox/app/mcp.py +4 -3
  39. cmdbox/app/options.py +8 -0
  40. cmdbox/app/web.py +58 -39
  41. cmdbox/extensions/features.yml +3 -0
  42. cmdbox/extensions/sample_project/sample/app/features/cli/sample_server_time.py +0 -9
  43. cmdbox/licenses/LICENSE_Mako_1_3_10_MIT_License.txt +19 -0
  44. cmdbox/licenses/LICENSE_alembic_1_16_5_UNKNOWN.txt +19 -0
  45. cmdbox/licenses/{LICENSE_cffi_1_17_1_MIT_License.txt → LICENSE_cffi_2_0_0_UNKNOWN.txt} +2 -5
  46. cmdbox/licenses/LICENSE_debugpy_1_8_17_MIT_License.txt +24 -0
  47. cmdbox/licenses/LICENSE_et_xmlfile_2_0_0_MIT_License.txt +298 -0
  48. cmdbox/licenses/LICENSE_fastuuid_0_13_5_BSD_License.txt +29 -0
  49. cmdbox/licenses/LICENSE_google-cloud-monitoring_2_27_2_Apache_Software_License.txt +202 -0
  50. cmdbox/licenses/LICENSE_google-cloud-spanner_3_58_0_Apache_Software_License.txt +202 -0
  51. cmdbox/licenses/LICENSE_google-genai_1_40_0_Apache_Software_License.txt +202 -0
  52. cmdbox/licenses/LICENSE_grpc-interceptor_0_15_4_MIT_License.txt +21 -0
  53. cmdbox/licenses/{LICENSE_lazy-object-proxy_1_11_0_BSD_License.txt → LICENSE_lazy-object-proxy_1_12_0_UNKNOWN.txt} +1 -1
  54. cmdbox/licenses/LICENSE_openpyxl_3_1_5_MIT_License.txt +23 -0
  55. cmdbox/licenses/LICENSE_opentelemetry-exporter-otlp-proto-common_1_37_0_UNKNOWN.txt +201 -0
  56. cmdbox/licenses/LICENSE_opentelemetry-exporter-otlp-proto-http_1_37_0_UNKNOWN.txt +201 -0
  57. cmdbox/licenses/LICENSE_opentelemetry-proto_1_37_0_UNKNOWN.txt +201 -0
  58. cmdbox/licenses/LICENSE_opentelemetry-sdk_1_37_0_UNKNOWN.txt +201 -0
  59. cmdbox/licenses/LICENSE_opentelemetry-semantic-conventions_0_58b0_UNKNOWN.txt +201 -0
  60. cmdbox/licenses/LICENSE_sqlalchemy-spanner_1_16_0_Apache_Software_License.txt +202 -0
  61. cmdbox/licenses/LICENSE_sqlparse_0_5_3_BSD_License.txt +25 -0
  62. cmdbox/licenses/{LICENSE_uvicorn_0_35_0_BSD_License.txt → LICENSE_uvicorn_0_37_0_BSD_License.txt} +2 -1
  63. cmdbox/licenses/files.txt +82 -71
  64. cmdbox/version.py +2 -2
  65. cmdbox/web/assets/cmdbox/svgicon.js +9 -0
  66. {cmdbox-0.6.4.2.dist-info → cmdbox-0.6.6.dist-info}/METADATA +29 -29
  67. {cmdbox-0.6.4.2.dist-info → cmdbox-0.6.6.dist-info}/RECORD +133 -117
  68. cmdbox/app/features/cli/cmdbox_vision_predict.py +0 -192
  69. cmdbox/licenses/LICENSE_APScheduler_3_11_0_MIT_License.txt +0 -19
  70. cmdbox/licenses/LICENSE_backoff_2_2_1_MIT_License.txt +0 -21
  71. cmdbox/licenses/LICENSE_fastapi-sso_0_18_0_MIT_License.txt +0 -21
  72. cmdbox/licenses/LICENSE_litellm-enterprise_0_1_19_UNKNOWN.txt +0 -37
  73. cmdbox/licenses/LICENSE_oauthlib_3_3_1_BSD-3-Clause.txt +0 -27
  74. cmdbox/licenses/LICENSE_orjson_3_11_1_Apache_Software_License-MIT_License.txt +0 -201
  75. /cmdbox/licenses/{LICENSE_Authlib_1_6_1_BSD_License.txt → LICENSE_Authlib_1_6_5_BSD_License.txt} +0 -0
  76. /cmdbox/licenses/{LICENSE_MarkupSafe_3_0_2_BSD_License.txt → LICENSE_MarkupSafe_3_0_3_UNKNOWN.txt} +0 -0
  77. /cmdbox/licenses/{LICENSE_PyYAML_6_0_2_MIT_License.txt → LICENSE_PyYAML_6_0_3_MIT_License.txt} +0 -0
  78. /cmdbox/licenses/{LICENSE_anyio_4_10_0_UNKNOWN.txt → LICENSE_anyio_4_11_0_UNKNOWN.txt} +0 -0
  79. /cmdbox/licenses/{LICENSE_cachetools_5_5_2_MIT_License.txt → LICENSE_cachetools_6_2_0_MIT_License.txt} +0 -0
  80. /cmdbox/licenses/{LICENSE_click_8_2_1_UNKNOWN.txt → LICENSE_click_8_3_0_UNKNOWN.txt} +0 -0
  81. /cmdbox/licenses/{LICENSE_cryptography_45_0_6_Apache-2_0_OR_BSD-3-Clause.txt → LICENSE_cryptography_46_0_2_UNKNOWN.txt} +0 -0
  82. /cmdbox/licenses/{LICENSE_cyclopts_3_22_5_Apache_Software_License.txt → LICENSE_cyclopts_3_24_0_Apache_Software_License.txt} +0 -0
  83. /cmdbox/licenses/{LICENSE_dnspython_2_7_0_ISC_License-ISCL.txt → LICENSE_dnspython_2_8_0_ISC_License-ISCL.txt} +0 -0
  84. /cmdbox/licenses/{LICENSE_email_validator_2_2_0_The_Unlicense-Unlicense.txt → LICENSE_email-validator_2_3_0_The_Unlicense-Unlicense.txt} +0 -0
  85. /cmdbox/licenses/{LICENSE_fastapi_0_116_1_MIT_License.txt → LICENSE_fastapi_0_118_0_MIT_License.txt} +0 -0
  86. /cmdbox/licenses/{LICENSE_fastmcp_2_11_3_Apache_Software_License.txt → LICENSE_fastmcp_2_12_4_Apache_Software_License.txt} +0 -0
  87. /cmdbox/licenses/{LICENSE_filelock_3_18_0_The_Unlicense-Unlicense.txt → LICENSE_filelock_3_19_1_The_Unlicense-Unlicense.txt} +0 -0
  88. /cmdbox/licenses/{LICENSE_fsspec_2025_7_0_BSD_License.txt → LICENSE_fsspec_2025_9_0_UNKNOWN.txt} +0 -0
  89. /cmdbox/licenses/{LICENSE_gevent_25_5_1_MIT.txt → LICENSE_gevent_25_9_1_MIT.txt} +0 -0
  90. /cmdbox/licenses/{LICENSE_google-adk_1_10_0_Apache_Software_License.txt → LICENSE_google-adk_1_15_1_Apache_Software_License.txt} +0 -0
  91. /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
  92. /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
  93. /cmdbox/licenses/{LICENSE_google-auth_2_40_3_Apache_Software_License.txt → LICENSE_google-auth_2_41_1_Apache_Software_License.txt} +0 -0
  94. /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
  95. /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
  96. /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
  97. /cmdbox/licenses/{LICENSE_grpcio-status_1_74_0_Apache_Software_License.txt → LICENSE_grpcio-status_1_75_1_Apache_Software_License.txt} +0 -0
  98. /cmdbox/licenses/{LICENSE_grpcio_1_74_0_Apache_Software_License.txt → LICENSE_grpcio_1_75_1_Apache_Software_License.txt} +0 -0
  99. /cmdbox/licenses/{LICENSE_httplib2_0_22_0_MIT_License.txt → LICENSE_httplib2_0_31_0_MIT_License.txt} +0 -0
  100. /cmdbox/licenses/{LICENSE_huggingface-hub_0_34_4_Apache_Software_License.txt → LICENSE_huggingface-hub_0_35_3_Apache_Software_License.txt} +0 -0
  101. /cmdbox/licenses/{LICENSE_jaraco_functools_4_2_1_UNKNOWN.txt → LICENSE_jaraco_functools_4_3_0_UNKNOWN.txt} +0 -0
  102. /cmdbox/licenses/{LICENSE_jiter_0_10_0_MIT_License.txt → LICENSE_jiter_0_11_0_MIT_License.txt} +0 -0
  103. /cmdbox/licenses/{LICENSE_jsonschema-specifications_2025_4_1_UNKNOWN.txt → LICENSE_jsonschema-specifications_2025_9_1_UNKNOWN.txt} +0 -0
  104. /cmdbox/licenses/{LICENSE_jsonschema_4_25_0_UNKNOWN.txt → LICENSE_jsonschema_4_25_1_UNKNOWN.txt} +0 -0
  105. /cmdbox/licenses/{LICENSE_litellm_1_75_5_post1_MIT_License.txt → LICENSE_litellm_1_77_5_MIT_License.txt} +0 -0
  106. /cmdbox/licenses/{LICENSE_mcp_1_12_4_MIT_License.txt → LICENSE_mcp_1_16_0_MIT_License.txt} +0 -0
  107. /cmdbox/licenses/{LICENSE_more-itertools_10_7_0_MIT_License.txt → LICENSE_more-itertools_10_8_0_UNKNOWN.txt} +0 -0
  108. /cmdbox/licenses/{LICENSE_numpy_2_3_2_BSD_License.txt → LICENSE_numpy_2_3_3_BSD_License.txt} +0 -0
  109. /cmdbox/licenses/{LICENSE_openai_1_99_9_Apache_Software_License.txt → LICENSE_openai_2_1_0_Apache_Software_License.txt} +0 -0
  110. /cmdbox/licenses/{LICENSE_opentelemetry-api_1_36_0_UNKNOWN.txt → LICENSE_opentelemetry-api_1_37_0_UNKNOWN.txt} +0 -0
  111. /cmdbox/licenses/{LICENSE_opentelemetry-sdk_1_36_0_UNKNOWN.txt → LICENSE_opentelemetry-exporter-gcp-logging_1_9_0a0_Apache_Software_License.txt} +0 -0
  112. /cmdbox/licenses/{LICENSE_opentelemetry-semantic-conventions_0_57b0_UNKNOWN.txt → LICENSE_opentelemetry-exporter-gcp-monitoring_1_9_0a0_Apache_Software_License.txt} +0 -0
  113. /cmdbox/licenses/{LICENSE_prompt_toolkit_3_0_51_BSD_License.txt → LICENSE_prompt_toolkit_3_0_52_BSD_License.txt} +0 -0
  114. /cmdbox/licenses/{LICENSE_protobuf_6_31_1_3-Clause_BSD_License.txt → LICENSE_protobuf_6_32_1_3-Clause_BSD_License.txt} +0 -0
  115. /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
  116. /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
  117. /cmdbox/licenses/{LICENSE_pycparser_2_22_BSD_License.txt → LICENSE_pycparser_2_23_BSD_License.txt} +0 -0
  118. /cmdbox/licenses/{LICENSE_pydantic-settings_2_10_1_MIT_License.txt → LICENSE_pydantic-settings_2_11_0_MIT_License.txt} +0 -0
  119. /cmdbox/licenses/{LICENSE_pydantic_2_11_7_MIT_License.txt → LICENSE_pydantic_2_11_10_MIT_License.txt} +0 -0
  120. /cmdbox/licenses/{LICENSE_pyparsing_3_2_3_MIT_License.txt → LICENSE_pyparsing_3_2_5_UNKNOWN.txt} +0 -0
  121. /cmdbox/licenses/{LICENSE_pyperclip_1_9_0_BSD_License.txt → LICENSE_pyperclip_1_11_0_BSD_License.txt} +0 -0
  122. /cmdbox/licenses/{LICENSE_questionary_2_1_0_MIT_License.txt → LICENSE_questionary_2_1_1_MIT_License.txt} +0 -0
  123. /cmdbox/licenses/{LICENSE_regex_2025_7_34_UNKNOWN.txt → LICENSE_regex_2025_9_18_UNKNOWN.txt} +0 -0
  124. /cmdbox/licenses/{LICENSE_requests_2_32_4_Apache_Software_License.txt → LICENSE_requests_2_32_5_Apache_Software_License.txt} +0 -0
  125. /cmdbox/licenses/{LICENSE_rpds-py_0_27_0_UNKNOWN.txt → LICENSE_rpds-py_0_27_1_UNKNOWN.txt} +0 -0
  126. /cmdbox/licenses/{LICENSE_shapely_2_1_1_BSD_License.txt → LICENSE_shapely_2_1_2_BSD_License.txt} +0 -0
  127. /cmdbox/licenses/{LICENSE_sphinx-sitemap_2_7_2_UNKNOWN.txt → LICENSE_sphinx-sitemap_2_8_0_UNKNOWN.txt} +0 -0
  128. /cmdbox/licenses/{LICENSE_starlette_0_47_2_BSD_License.txt → LICENSE_starlette_0_48_0_BSD_License.txt} +0 -0
  129. /cmdbox/licenses/{LICENSE_tenacity_9_1_2_Apache_Software_License.txt → LICENSE_tenacity_8_5_0_Apache_Software_License.txt} +0 -0
  130. /cmdbox/licenses/{LICENSE_tokenizers_0_21_4_Apache_Software_License.txt → LICENSE_tokenizers_0_22_1_Apache_Software_License.txt} +0 -0
  131. /cmdbox/licenses/{LICENSE_twine_6_1_0_Apache_Software_License.txt → LICENSE_twine_6_2_0_UNKNOWN.txt} +0 -0
  132. /cmdbox/licenses/{LICENSE_typing-inspection_0_4_1_UNKNOWN.txt → LICENSE_typing-inspection_0_4_2_UNKNOWN.txt} +0 -0
  133. /cmdbox/licenses/{LICENSE_typing_extensions_4_14_1_UNKNOWN.txt → LICENSE_typing_extensions_4_15_0_UNKNOWN.txt} +0 -0
  134. /cmdbox/licenses/{LICENSE_wcwidth_0_2_13_MIT_License.txt → LICENSE_wcwidth_0_2_14_MIT_License.txt} +0 -0
  135. /cmdbox/licenses/{LICENSE_zope_event_5_1_1_Zope_Public_License.txt → LICENSE_zope_event_6_0_Zope_Public_License.txt} +0 -0
  136. /cmdbox/licenses/{LICENSE_zope_interface_7_2_Zope_Public_License.txt → LICENSE_zope_interface_8_0_1_Zope_Public_License.txt} +0 -0
  137. {cmdbox-0.6.4.2.dist-info → cmdbox-0.6.6.dist-info}/WHEEL +0 -0
  138. {cmdbox-0.6.4.2.dist-info → cmdbox-0.6.6.dist-info}/entry_points.txt +0 -0
  139. {cmdbox-0.6.4.2.dist-info → cmdbox-0.6.6.dist-info}/licenses/LICENSE +0 -0
  140. {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
- for k in opt.keys():
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 = self.load_cmd(web, title)
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 = self.load_cmd(web, title)
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 = self.load_cmd(web, title)
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 = self.load_cmd(web, title)
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 req.session is not None and 'signin' in req.session and req.session['signin'] is not None:
151
- if 'clmsg_id' in req.session['signin'] and req.session['signin']['clmsg_id'] is not None:
152
- opt['clmsg_id'] = req.session['signin']['clmsg_id']
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
- with open(abspath, "rb") as f:
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
- with open(save_path, "wb") as f:
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
- with open(args.llmsvaccountfile, "r", encoding="utf-8") as f:
190
- vertex_credentials = json.load(f)
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
- guvicorn_workers:int=-1, guvicorn_timeout:int=30,
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
- guvicorn_workers (int, optional): Gunicornワーカー数. Defaults to -1.
741
- guvicorn_timeout (int, optional): Gunicornタイムアウト. Defaults to 30.
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.guvicorn_workers = guvicorn_workers
758
- self.guvicorn_timeout = guvicorn_timeout
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: guvicorn_worker={self.guvicorn_workers}")
775
- self.logger.debug(f"web start parameter: guvicorn_timeout={self.guvicorn_timeout}")
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 = ThreadedUvicorn(self.logger, config=https_config,
898
- guvicorn_config=dict(workers=self.guvicorn_workers, timeout=self.guvicorn_timeout))
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 = ThreadedUvicorn(self.logger, config=http_config,
904
- guvicorn_config=dict(workers=self.guvicorn_workers, timeout=self.guvicorn_timeout))
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
- with open("web.pid", mode="w", encoding="utf-8") as f:
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
- with open("web.pid", mode="r", encoding="utf-8") as f:
930
+ def _r(f):
930
931
  pid = f.read()
931
932
  if pid != "":
932
- os.kill(int(pid), signal.CTRL_C_EVENT)
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 ThreadedUvicorn:
943
- def __init__(self, logger:logging.Logger, config:Config, guvicorn_config:Dict[str, Any]=None, force_uvicorn:bool=False):
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.guvicorn_config = guvicorn_config
946
- self.force_uvicorn = True if platform.system() == "Windows" else force_uvicorn
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.force_uvicorn:
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="uvicorn.workers.UvicornWorker",
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.guvicorn_config = self.guvicorn_config or {}
980
- if 'workers' not in self.guvicorn_config:
981
- self.guvicorn_config['workers'] = None
982
- if self.guvicorn_config['workers'] is None or self.guvicorn_config['workers'] <= 0:
983
- self.guvicorn_config['workers'] = multiprocessing.cpu_count()*2
984
- if 'timeout' not in self.guvicorn_config:
985
- self.guvicorn_config['timeout'] = None
986
- if self.guvicorn_config['timeout'] is None or self.guvicorn_config['timeout'] <= 0:
987
- self.guvicorn_config['timeout'] = 30
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.guvicorn_config}
990
- self.server = App(config.app, opt)
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.force_uvicorn:
995
- asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
996
- self.thread.start()
997
- asyncio.run(self.wait_for_started())
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.force_uvicorn:
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.force_uvicorn:
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"
@@ -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