cmdbox 0.5.1.2__py3-none-any.whl → 0.5.2__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 +4 -2
- cmdbox/app/auth/signin.py +633 -631
- cmdbox/app/client.py +10 -10
- cmdbox/app/common.py +50 -6
- cmdbox/app/commons/convert.py +9 -0
- cmdbox/app/commons/module.py +113 -113
- cmdbox/app/commons/redis_client.py +40 -29
- cmdbox/app/edge.py +4 -4
- cmdbox/app/features/cli/audit_base.py +135 -0
- cmdbox/app/features/cli/cmdbox_audit_createdb.py +224 -0
- cmdbox/app/features/cli/cmdbox_audit_delete.py +299 -0
- cmdbox/app/features/cli/cmdbox_audit_search.py +350 -0
- cmdbox/app/features/cli/cmdbox_audit_write.py +240 -0
- cmdbox/app/features/cli/cmdbox_client_file_copy.py +207 -207
- cmdbox/app/features/cli/cmdbox_client_file_download.py +207 -207
- cmdbox/app/features/cli/cmdbox_client_file_list.py +193 -193
- cmdbox/app/features/cli/cmdbox_client_file_mkdir.py +191 -191
- cmdbox/app/features/cli/cmdbox_client_file_move.py +199 -199
- cmdbox/app/features/cli/cmdbox_client_file_remove.py +190 -190
- cmdbox/app/features/cli/cmdbox_client_file_rmdir.py +190 -190
- cmdbox/app/features/cli/cmdbox_client_file_upload.py +212 -212
- cmdbox/app/features/cli/cmdbox_client_server_info.py +166 -166
- cmdbox/app/features/cli/cmdbox_server_list.py +88 -88
- cmdbox/app/features/cli/cmdbox_server_stop.py +138 -138
- cmdbox/app/features/web/cmdbox_web_del_cmd.py +2 -0
- cmdbox/app/features/web/cmdbox_web_del_pipe.py +1 -0
- cmdbox/app/features/web/cmdbox_web_do_signin.py +12 -2
- cmdbox/app/features/web/cmdbox_web_do_signout.py +1 -0
- cmdbox/app/features/web/cmdbox_web_exec_cmd.py +25 -1
- cmdbox/app/features/web/cmdbox_web_exec_pipe.py +1 -0
- cmdbox/app/features/web/cmdbox_web_filer download.py +43 -42
- cmdbox/app/features/web/cmdbox_web_filer.py +1 -0
- cmdbox/app/features/web/cmdbox_web_filer_upload.py +65 -64
- cmdbox/app/features/web/cmdbox_web_gui.py +166 -165
- cmdbox/app/features/web/cmdbox_web_load_pin.py +43 -43
- cmdbox/app/features/web/cmdbox_web_raw_pipe.py +87 -87
- cmdbox/app/features/web/cmdbox_web_save_cmd.py +1 -0
- cmdbox/app/features/web/cmdbox_web_save_pin.py +42 -42
- cmdbox/app/features/web/cmdbox_web_save_pipe.py +1 -0
- cmdbox/app/features/web/cmdbox_web_users.py +12 -0
- cmdbox/app/options.py +767 -601
- cmdbox/extensions/features.yml +20 -0
- cmdbox/extensions/sample_project/sample/app/features/cli/sample_client_time.py +82 -82
- cmdbox/extensions/sample_project/sample/app/features/cli/sample_server_time.py +145 -145
- cmdbox/licenses/{LICENSE.Sphinx.8.1.3(BSD License).txt → LICENSE.Sphinx.8.2.3(UNKNOWN).txt} +1 -1
- cmdbox/licenses/{LICENSE.babel.2.16.0(BSD License).txt → LICENSE.babel.2.17.0(BSD License).txt } +1 -1
- cmdbox/licenses/{LICENSE.pkginfo.1.10.0(MIT License).txt → LICENSE.charset-normalizer.3.4.1(MIT License).txt } +1 -1
- cmdbox/licenses/LICENSE.gunicorn.23.0.0(MIT License).txt +23 -0
- cmdbox/licenses/LICENSE.importlib_metadata.8.6.1(Apache Software License).txt +202 -0
- cmdbox/licenses/LICENSE.nh3.0.2.21(MIT).txt +21 -0
- cmdbox/licenses/{LICENSE.pillow.11.0.0(CMU License (MIT-CMU)).txt → LICENSE.pillow.11.1.0(CMU License (MIT-CMU)).txt } +27 -40
- cmdbox/licenses/LICENSE.plyer.2.1.0(MIT License).txt +19 -0
- cmdbox/licenses/LICENSE.prompt_toolkit.3.0.50(BSD License).txt +27 -0
- cmdbox/licenses/LICENSE.psycopg-binary.3.2.6(GNU Lesser General Public License v3 (LGPLv3)).txt +165 -0
- cmdbox/licenses/LICENSE.psycopg-pool.3.2.6(GNU Lesser General Public License v3 (LGPLv3)).txt +165 -0
- cmdbox/licenses/LICENSE.psycopg.3.2.6(GNU Lesser General Public License v3 (LGPLv3)).txt +165 -0
- cmdbox/licenses/LICENSE.pycryptodome.3.22.0(BSD License; Public Domain).txt +61 -0
- cmdbox/licenses/LICENSE.pystray.0.19.5(GNU Lesser General Public License v3 (LGPLv3)).txt +674 -0
- cmdbox/licenses/LICENSE.questionary.2.1.0(MIT License).txt +19 -0
- cmdbox/licenses/LICENSE.roman-numerals-py.3.1.0(CC0 1.0 Universal (CC0 1.0) Public Domain Dedication; Zero-Clause BSD (0BSD)).txt +146 -0
- cmdbox/licenses/{LICENSE.six.1.16.0(MIT License).txt → LICENSE.six.1.17.0(MIT License).txt } +1 -1
- cmdbox/licenses/{LICENSE.charset-normalizer.3.4.0(MIT License).txt → LICENSE.typing-inspection.0.4.0(MIT License).txt } +2 -2
- cmdbox/licenses/LICENSE.tzdata.2025.2(Apache Software License).txt +15 -0
- cmdbox/licenses/files.txt +48 -36
- cmdbox/logconf_audit.yml +30 -0
- cmdbox/logconf_cmdbox.yml +30 -0
- cmdbox/version.py +2 -2
- cmdbox/web/assets/cmdbox/color_mode.css +516 -0
- cmdbox/web/assets/cmdbox/common.js +19 -0
- cmdbox/web/assets/cmdbox/list_cmd.js +9 -10
- cmdbox/web/assets/cmdbox/main.js +2 -2
- cmdbox/web/assets/cmdbox/result.js +2 -2
- cmdbox/web/assets/cmdbox/signin.js +2 -2
- cmdbox/web/assets/cmdbox/users.js +2 -3
- cmdbox/web/assets/cmdbox/view_result.js +1 -1
- cmdbox/web/assets/filer/main.js +2 -2
- cmdbox/web/filer.html +16 -2
- cmdbox/web/gui.html +15 -1
- cmdbox/web/result.html +15 -1
- cmdbox/web/signin.html +35 -14
- cmdbox/web/users.html +15 -1
- {cmdbox-0.5.1.2.dist-info → cmdbox-0.5.2.dist-info}/METADATA +25 -5
- {cmdbox-0.5.1.2.dist-info → cmdbox-0.5.2.dist-info}/RECORD +116 -96
- {cmdbox-0.5.1.2.dist-info → cmdbox-0.5.2.dist-info}/entry_points.txt +0 -1
- cmdbox/licenses/LICENSE.nh3.0.2.18(MIT).txt +0 -1
- /cmdbox/licenses/{LICENSE.Jinja2.3.1.4(BSD License).txt → LICENSE.Jinja2.3.1.6(BSD License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.Pygments.2.18.0(BSD License).txt → LICENSE.Pygments.2.19.1(BSD License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.anyio.4.6.2.post1(MIT License).txt → LICENSE.anyio.4.9.0(MIT License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.argcomplete.3.5.1(Apache Software License).txt → LICENSE.argcomplete.3.6.1(Apache Software License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.certifi.2024.8.30(Mozilla Public License 2.0 (MPL 2.0)).txt → LICENSE.certifi.2025.1.31(Mozilla Public License 2.0 (MPL 2.0)).txt} +0 -0
- /cmdbox/licenses/{LICENSE.click.8.1.7(BSD License).txt → LICENSE.click.8.1.8(BSD License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.cryptography.43.0.3(Apache Software License; BSD License).txt → LICENSE.cryptography.44.0.2(Apache Software License; BSD License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.fastapi.0.115.5(MIT License).txt → LICENSE.fastapi.0.115.12(MIT License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.importlib_metadata.8.5.0(Apache Software License).txt → LICENSE.id.1.5.0(Apache Software License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.keyring.25.5.0(MIT License).txt → LICENSE.keyring.25.6.0(MIT License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.more-itertools.10.5.0(MIT License).txt → LICENSE.more-itertools.10.6.0(MIT License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.numpy.2.1.3(BSD License).txt → LICENSE.numpy.2.2.4(BSD License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.prettytable.3.12.0(BSD License).txt → LICENSE.prettytable.3.16.0(UNKNOWN).txt} +0 -0
- /cmdbox/licenses/{LICENSE.pydantic.2.10.2(MIT License).txt → LICENSE.pydantic.2.11.1(MIT License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.pydantic_core.2.27.1(MIT License).txt → LICENSE.pydantic_core.2.33.0(MIT License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.python-dotenv.1.0.1(BSD License).txt → LICENSE.python-dotenv.1.1.0(BSD License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.python-multipart.0.0.17(Apache Software License).txt → LICENSE.python-multipart.0.0.20(Apache Software License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.redis.5.2.0(MIT License).txt → LICENSE.redis.5.2.1(MIT License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.rich.13.9.4(MIT License).txt → LICENSE.rich.14.0.0(MIT License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.sphinx-intl.2.3.0(BSD License).txt → LICENSE.sphinx-intl.2.3.1(BSD License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.starlette.0.41.3(BSD License).txt → LICENSE.starlette.0.46.1(BSD License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.tomli.2.1.0(MIT License).txt → LICENSE.tomli.2.2.1(MIT License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.twine.5.1.1(Apache Software License).txt → LICENSE.twine.6.1.0(Apache Software License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.typing_extensions.4.12.2(Python Software Foundation License).txt → LICENSE.typing_extensions.4.13.0(UNKNOWN).txt} +0 -0
- /cmdbox/licenses/{LICENSE.urllib3.2.2.3(MIT License).txt → LICENSE.urllib3.2.3.0(MIT License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.uvicorn.0.32.1(BSD License).txt → LICENSE.uvicorn.0.34.0(BSD License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.watchfiles.1.0.0(MIT License).txt → LICENSE.watchfiles.1.0.4(MIT License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.websockets.14.1(BSD License).txt → LICENSE.websockets.15.0.1(BSD License).txt} +0 -0
- /cmdbox/licenses/{LICENSE.zope.interface.7.1.1(Zope Public License).txt → LICENSE.zope.interface.7.2(Zope Public License).txt} +0 -0
- {cmdbox-0.5.1.2.dist-info → cmdbox-0.5.2.dist-info}/LICENSE +0 -0
- {cmdbox-0.5.1.2.dist-info → cmdbox-0.5.2.dist-info}/WHEEL +0 -0
- {cmdbox-0.5.1.2.dist-info → cmdbox-0.5.2.dist-info}/top_level.txt +0 -0
cmdbox/app/client.py
CHANGED
|
@@ -28,7 +28,7 @@ class Client(object):
|
|
|
28
28
|
def __exit__(self, a, b, c):
|
|
29
29
|
pass
|
|
30
30
|
|
|
31
|
-
def stop_server(self, retry_count:int=3, retry_interval:int=5, timeout:int
|
|
31
|
+
def stop_server(self, retry_count:int=3, retry_interval:int=5, timeout:int=60):
|
|
32
32
|
"""
|
|
33
33
|
Redisサーバーを停止する
|
|
34
34
|
|
|
@@ -44,7 +44,7 @@ class Client(object):
|
|
|
44
44
|
return res_json
|
|
45
45
|
|
|
46
46
|
def file_list(self, svpath:str, recursive:bool, scope:str="client", client_data:Path = None,
|
|
47
|
-
retry_count:int=3, retry_interval:int=5, timeout:int
|
|
47
|
+
retry_count:int=3, retry_interval:int=5, timeout:int=60):
|
|
48
48
|
"""
|
|
49
49
|
サーバー上のファイルリストを取得する
|
|
50
50
|
|
|
@@ -81,7 +81,7 @@ class Client(object):
|
|
|
81
81
|
return dict(warn=f"scope is invalid. {scope}")
|
|
82
82
|
|
|
83
83
|
def file_mkdir(self, svpath:str, scope:str="client", client_data:Path = None,
|
|
84
|
-
retry_count:int=3, retry_interval:int=5, timeout:int
|
|
84
|
+
retry_count:int=3, retry_interval:int=5, timeout:int=60):
|
|
85
85
|
"""
|
|
86
86
|
サーバー上にディレクトリを作成する
|
|
87
87
|
|
|
@@ -117,7 +117,7 @@ class Client(object):
|
|
|
117
117
|
return dict(warn=f"scope is invalid. {scope}")
|
|
118
118
|
|
|
119
119
|
def file_rmdir(self, svpath:str, scope:str="client", client_data:Path = None,
|
|
120
|
-
retry_count:int=3, retry_interval:int=5, timeout:int
|
|
120
|
+
retry_count:int=3, retry_interval:int=5, timeout:int=60):
|
|
121
121
|
"""
|
|
122
122
|
サーバー上のディレクトリを削除する
|
|
123
123
|
|
|
@@ -153,7 +153,7 @@ class Client(object):
|
|
|
153
153
|
return dict(warn=f"scope is invalid. {scope}")
|
|
154
154
|
|
|
155
155
|
def file_download(self, svpath:str, download_file:Path, scope:str="client", client_data:Path = None, rpath:str="", img_thumbnail:float=0.0,
|
|
156
|
-
retry_count:int=3, retry_interval:int=5, timeout:int
|
|
156
|
+
retry_count:int=3, retry_interval:int=5, timeout:int=60):
|
|
157
157
|
"""
|
|
158
158
|
サーバー上のファイルをダウンロードする
|
|
159
159
|
|
|
@@ -203,7 +203,7 @@ class Client(object):
|
|
|
203
203
|
return res_json
|
|
204
204
|
|
|
205
205
|
def file_upload(self, svpath:str, upload_file:Path, scope:str="client", client_data:Path=None, mkdir:bool=False, orverwrite:bool=False,
|
|
206
|
-
retry_count:int=3, retry_interval:int=5, timeout:int
|
|
206
|
+
retry_count:int=3, retry_interval:int=5, timeout:int=60):
|
|
207
207
|
"""
|
|
208
208
|
サーバー上にファイルをアップロードする
|
|
209
209
|
|
|
@@ -257,7 +257,7 @@ class Client(object):
|
|
|
257
257
|
return dict(warn=f"scope is invalid. {scope}")
|
|
258
258
|
|
|
259
259
|
def file_remove(self, svpath:str, scope:str="client", client_data:Path = None,
|
|
260
|
-
retry_count:int=3, retry_interval:int=5, timeout:int
|
|
260
|
+
retry_count:int=3, retry_interval:int=5, timeout:int=60):
|
|
261
261
|
"""
|
|
262
262
|
サーバー上のファイルを削除する
|
|
263
263
|
|
|
@@ -293,7 +293,7 @@ class Client(object):
|
|
|
293
293
|
return dict(warn=f"scope is invalid. {scope}")
|
|
294
294
|
|
|
295
295
|
def file_copy(self, from_path:str, to_path:str, orverwrite:bool=False, scope:str="client", client_data:Path = None,
|
|
296
|
-
retry_count:int=3, retry_interval:int=5, timeout:int
|
|
296
|
+
retry_count:int=3, retry_interval:int=5, timeout:int=60):
|
|
297
297
|
"""
|
|
298
298
|
サーバー上のファイルをコピーする
|
|
299
299
|
|
|
@@ -331,7 +331,7 @@ class Client(object):
|
|
|
331
331
|
return dict(warn=f"scope is invalid. {scope}")
|
|
332
332
|
|
|
333
333
|
def file_move(self, from_path:str, to_path:str, scope:str="client", client_data:Path = None,
|
|
334
|
-
retry_count:int=3, retry_interval:int=5, timeout:int
|
|
334
|
+
retry_count:int=3, retry_interval:int=5, timeout:int=60):
|
|
335
335
|
"""
|
|
336
336
|
サーバー上のファイルを移動する
|
|
337
337
|
|
|
@@ -367,7 +367,7 @@ class Client(object):
|
|
|
367
367
|
self.logger.warning(f"scope is invalid. {scope}")
|
|
368
368
|
return dict(warn=f"scope is invalid. {scope}")
|
|
369
369
|
|
|
370
|
-
def server_info(self, retry_count:int=3, retry_interval:int=5, timeout:int
|
|
370
|
+
def server_info(self, retry_count:int=3, retry_interval:int=5, timeout:int=60):
|
|
371
371
|
"""
|
|
372
372
|
サーバーの情報を取得する
|
|
373
373
|
|
cmdbox/app/common.py
CHANGED
|
@@ -36,7 +36,7 @@ def copy_sample(data:Path, ver=version):
|
|
|
36
36
|
data (Path): データディレクトリ
|
|
37
37
|
ver (version, optional): バージョン. Defaults to version
|
|
38
38
|
"""
|
|
39
|
-
|
|
39
|
+
dst_sample = Path(data) / '.samples' if data is not None else HOME_DIR / '.samples'
|
|
40
40
|
#if dst.exists():
|
|
41
41
|
# return
|
|
42
42
|
src = Path(ver.__file__).parent / 'extensions'
|
|
@@ -44,7 +44,13 @@ def copy_sample(data:Path, ver=version):
|
|
|
44
44
|
p = Path(dst)
|
|
45
45
|
if not p.exists():
|
|
46
46
|
shutil.copy2(src, dst)
|
|
47
|
-
shutil.copytree(src,
|
|
47
|
+
shutil.copytree(src, dst_sample, dirs_exist_ok=True, copy_function=copy)
|
|
48
|
+
dst_config = Path(data) / f'.{ver.__appid__}' if data is not None else HOME_DIR / f'.{ver.__appid__}'
|
|
49
|
+
dst_config.mkdir(parents=True, exist_ok=True)
|
|
50
|
+
if not (dst_config / 'features.yml').exists():
|
|
51
|
+
shutil.copy2(src / 'features.yml', dst_config / 'features.yml')
|
|
52
|
+
if not (dst_config / 'user_list.yml').exists():
|
|
53
|
+
shutil.copy2(src / 'user_list.yml', dst_config / 'user_list.yml')
|
|
48
54
|
|
|
49
55
|
def mklogdir(data:Path) -> Path:
|
|
50
56
|
"""
|
|
@@ -200,7 +206,9 @@ def default_json_enc(o) -> Any:
|
|
|
200
206
|
if isinstance(o, tempfile._TemporaryFileWrapper):
|
|
201
207
|
return str(o)
|
|
202
208
|
if isinstance(o, datetime.datetime):
|
|
203
|
-
|
|
209
|
+
if o.tzinfo is datetime.timezone.utc:
|
|
210
|
+
return o.strftime('%Y-%m-%dT%H:%M:%S')
|
|
211
|
+
return o.strftime('%Y-%m-%dT%H:%M:%S%z')
|
|
204
212
|
if isinstance(o, feature.Feature):
|
|
205
213
|
return 'object'
|
|
206
214
|
raise TypeError(f"Type {type(o)} not serializable")
|
|
@@ -389,11 +397,11 @@ def print_format(data:dict, format:bool, tm:float, output_json:str=None, output_
|
|
|
389
397
|
Returns:
|
|
390
398
|
str: 生成された文字列
|
|
391
399
|
"""
|
|
392
|
-
if type(data) is dict and "success" in data and "performance" in data["success"] and type(data["success"]["performance"]) is list and pf is not None:
|
|
400
|
+
if type(data) is dict and "success" in data and type(data["success"]) is dict and "performance" in data["success"] and type(data["success"]["performance"]) is list and pf is not None:
|
|
393
401
|
data["success"]["performance"] += pf
|
|
394
402
|
txt = ''
|
|
395
403
|
if format:
|
|
396
|
-
if 'success' in data:
|
|
404
|
+
if 'success' in data and type(data["success"]) is dict:
|
|
397
405
|
data = data['success']['data'] if 'data' in data['success'] else data['success']
|
|
398
406
|
if type(data) == list:
|
|
399
407
|
txt = tabulate(data, headers='keys', tablefmt=tablefmt)
|
|
@@ -412,7 +420,7 @@ def print_format(data:dict, format:bool, tm:float, output_json:str=None, output_
|
|
|
412
420
|
except BrokenPipeError:
|
|
413
421
|
pass
|
|
414
422
|
else:
|
|
415
|
-
if 'success' in data and type(data['success'])
|
|
423
|
+
if 'success' in data and type(data['success']) is dict:
|
|
416
424
|
if "performance" not in data["success"]:
|
|
417
425
|
data["success"]["performance"] = []
|
|
418
426
|
performance = data["success"]["performance"]
|
|
@@ -559,6 +567,23 @@ def decrypt(enc_message:str, password:str) -> str:
|
|
|
559
567
|
except:
|
|
560
568
|
return None
|
|
561
569
|
|
|
570
|
+
def chopdq(target:str):
|
|
571
|
+
""""
|
|
572
|
+
"で囲まれた文字列を取り除きます。
|
|
573
|
+
targetにNoneが指定された場合はNoneを返します。
|
|
574
|
+
|
|
575
|
+
Args:
|
|
576
|
+
target (str): 対象文字列
|
|
577
|
+
|
|
578
|
+
Returns:
|
|
579
|
+
str: 取り除かれた文字列
|
|
580
|
+
"""
|
|
581
|
+
if target is None:
|
|
582
|
+
return None
|
|
583
|
+
if not isinstance(target, str):
|
|
584
|
+
return target
|
|
585
|
+
return target[1:-1] if target.startswith('"') and target.endswith('"') else target
|
|
586
|
+
|
|
562
587
|
def is_event_loop_running() -> bool:
|
|
563
588
|
"""
|
|
564
589
|
イベントループが実行中かどうかを取得します。
|
|
@@ -594,3 +619,22 @@ def exec_sync(func, *args, **kwargs) -> Any:
|
|
|
594
619
|
return ret
|
|
595
620
|
return asyncio.run(func(*args, **kwargs))
|
|
596
621
|
return func(*args, **kwargs)
|
|
622
|
+
|
|
623
|
+
def get_tzoffset_str() -> str:
|
|
624
|
+
"""
|
|
625
|
+
タイムゾーンのオフセットを取得します
|
|
626
|
+
|
|
627
|
+
Returns:
|
|
628
|
+
str: タイムゾーンのオフセット
|
|
629
|
+
"""
|
|
630
|
+
# 現在のタイムゾーンのオフセットを取得
|
|
631
|
+
now_utc = datetime.datetime.now(datetime.timezone.utc)
|
|
632
|
+
now_local = now_utc.astimezone()
|
|
633
|
+
offset = now_local.utcoffset()
|
|
634
|
+
# timedelta オブジェクトから '+/-HH:MM' 形式の文字列を生成
|
|
635
|
+
total_seconds = int(offset.total_seconds())
|
|
636
|
+
hours = abs(total_seconds) // 3600
|
|
637
|
+
minutes = (abs(total_seconds) % 3600) // 60
|
|
638
|
+
sign = "+" if total_seconds >= 0 else "-"
|
|
639
|
+
|
|
640
|
+
return f"{sign}{hours:02}:{minutes:02}"
|
cmdbox/app/commons/convert.py
CHANGED
|
@@ -227,6 +227,7 @@ def img2byte(image:Image.Image, format:str='jpeg') -> bytes:
|
|
|
227
227
|
def str2b64str(s:str) -> str:
|
|
228
228
|
"""
|
|
229
229
|
文字列をBase64エンコードします。
|
|
230
|
+
sにNoneを指定した場合はNoneを返します。
|
|
230
231
|
|
|
231
232
|
Args:
|
|
232
233
|
s (str): 文字列
|
|
@@ -234,11 +235,15 @@ def str2b64str(s:str) -> str:
|
|
|
234
235
|
Returns:
|
|
235
236
|
str: Base64エンコードされた文字列
|
|
236
237
|
"""
|
|
238
|
+
if s is None:
|
|
239
|
+
return None
|
|
237
240
|
return base64.b64encode(s.encode()).decode('utf-8')
|
|
238
241
|
|
|
239
242
|
def b64str2str(b64str:str) -> str:
|
|
240
243
|
"""
|
|
241
244
|
Base64エンコードされた文字列をデコードします。
|
|
245
|
+
b64strにNoneを指定した場合はNoneを返します。
|
|
246
|
+
b64strに'None'を指定した場合はNoneを返します。
|
|
242
247
|
|
|
243
248
|
Args:
|
|
244
249
|
b64str (str): Base64エンコードされた文字列
|
|
@@ -246,4 +251,8 @@ def b64str2str(b64str:str) -> str:
|
|
|
246
251
|
Returns:
|
|
247
252
|
str: デコードされた文字列
|
|
248
253
|
"""
|
|
254
|
+
if b64str is None:
|
|
255
|
+
return None
|
|
256
|
+
if b64str == 'None':
|
|
257
|
+
return None
|
|
249
258
|
return base64.b64decode(b64str).decode('utf-8')
|
cmdbox/app/commons/module.py
CHANGED
|
@@ -1,114 +1,114 @@
|
|
|
1
|
-
from cmdbox.app import feature
|
|
2
|
-
from typing import List, Dict, Any
|
|
3
|
-
import importlib.util
|
|
4
|
-
import inspect
|
|
5
|
-
import logging
|
|
6
|
-
import pkgutil
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def get_module_list(package_name) -> List[str]:
|
|
10
|
-
"""
|
|
11
|
-
パッケージ内のモジュール名のリストを取得します。
|
|
12
|
-
|
|
13
|
-
Args:
|
|
14
|
-
package_name (str): パッケージ名
|
|
15
|
-
|
|
16
|
-
Returns:
|
|
17
|
-
List[str]: モジュール名のリスト
|
|
18
|
-
"""
|
|
19
|
-
package = __import__(package_name, fromlist=[''])
|
|
20
|
-
return [name for _, name, _ in pkgutil.iter_modules(package.__path__)]
|
|
21
|
-
|
|
22
|
-
def load_features(package_name:str, prefix:str="cmdbox_", excludes:list=[], appcls=None, ver=None) -> Dict[str, Any]:
|
|
23
|
-
"""
|
|
24
|
-
フィーチャーを読み込みます。
|
|
25
|
-
|
|
26
|
-
Args:
|
|
27
|
-
package_name (str): パッケージ名
|
|
28
|
-
prefix (str, optional): プレフィックス. Defaults to "cmdbox_".
|
|
29
|
-
excludes (list, optional): 除外するモジュール名のリスト. Defaults to [].
|
|
30
|
-
appcls ([type], optional): アプリケーションクラス. Defaults to None.
|
|
31
|
-
ver ([type], optional): バージョンモジュール. Defaults to None.
|
|
32
|
-
Returns:
|
|
33
|
-
Dict[str, Any]: フィーチャーのリスト
|
|
34
|
-
"""
|
|
35
|
-
features = dict()
|
|
36
|
-
package = __import__(package_name, fromlist=[''])
|
|
37
|
-
for finder, name, ispkg in pkgutil.iter_modules(package.__path__):
|
|
38
|
-
if name.startswith(prefix):
|
|
39
|
-
if name in excludes:
|
|
40
|
-
continue
|
|
41
|
-
mod = importlib.import_module(f"{package_name}.{name}")
|
|
42
|
-
members = inspect.getmembers(mod, inspect.isclass)
|
|
43
|
-
for name, cls in members:
|
|
44
|
-
if cls is feature.Feature or not issubclass(cls, feature.Feature):
|
|
45
|
-
continue
|
|
46
|
-
fobj = cls(appcls, ver)
|
|
47
|
-
mode = fobj.get_mode()
|
|
48
|
-
if type(mode) is str:
|
|
49
|
-
cmd = fobj.get_cmd()
|
|
50
|
-
if mode not in features:
|
|
51
|
-
features[mode] = dict()
|
|
52
|
-
features[mode][cmd] = fobj.get_option()
|
|
53
|
-
if features[mode][cmd] is None:
|
|
54
|
-
raise ValueError(f'load_features: Cannot get options from {fobj}. The get_option() method returns None.')
|
|
55
|
-
features[mode][cmd]['feature'] = fobj
|
|
56
|
-
elif type(mode) is list:
|
|
57
|
-
for m in mode:
|
|
58
|
-
cmd = fobj.get_cmd()
|
|
59
|
-
if m not in features:
|
|
60
|
-
features[m] = dict()
|
|
61
|
-
features[m][cmd] = fobj.get_option()
|
|
62
|
-
if features[m][cmd] is None:
|
|
63
|
-
raise ValueError(f'load_features: Cannot get options from {fobj}. The get_option() method returns None.')
|
|
64
|
-
features[m][cmd]['feature'] = fobj
|
|
65
|
-
return features
|
|
66
|
-
|
|
67
|
-
def load_webfeatures(package_name:str, prefix:str="cmdbox_web_", excludes:list=[], appcls=None, ver=None, logger:logging.Logger=None) -> List[Any]:
|
|
68
|
-
"""
|
|
69
|
-
Webフィーチャーを読み込みます。
|
|
70
|
-
|
|
71
|
-
Args:
|
|
72
|
-
package_name (str): パッケージ名
|
|
73
|
-
prefix (str, optional): プレフィックス. Defaults to "cmdbox_web_".
|
|
74
|
-
excludes (list, optional): 除外するモジュール名のリスト. Defaults to [].
|
|
75
|
-
appcls ([type], optional): アプリケーションクラス. Defaults to None.
|
|
76
|
-
ver ([type], optional): バージョンモジュール. Defaults to None.
|
|
77
|
-
logger ([type], optional): ロガー. Defaults to None.
|
|
78
|
-
Returns:
|
|
79
|
-
Dict[feature.WebFeature]: Webフィーチャーのリスト
|
|
80
|
-
"""
|
|
81
|
-
webfeatures = list()
|
|
82
|
-
package = __import__(package_name, fromlist=[''])
|
|
83
|
-
for finder, name, ispkg in pkgutil.iter_modules(package.__path__):
|
|
84
|
-
if name.startswith(prefix):
|
|
85
|
-
if name in excludes:
|
|
86
|
-
if logger.level == logging.DEBUG:
|
|
87
|
-
logger.warning(f'load_webfeatures: {name} is excludes feature.')
|
|
88
|
-
continue
|
|
89
|
-
mod = importlib.import_module(f"{package_name}.{name}")
|
|
90
|
-
members = inspect.getmembers(mod, inspect.isclass)
|
|
91
|
-
for name, cls in members:
|
|
92
|
-
if cls is feature.WebFeature or not issubclass(cls, feature.WebFeature):
|
|
93
|
-
continue
|
|
94
|
-
fobj = cls(appcls, ver)
|
|
95
|
-
if logger is not None and logger.level == logging.DEBUG:
|
|
96
|
-
logger.debug(f'load_webfeatures: {fobj}')
|
|
97
|
-
webfeatures.append(fobj)
|
|
98
|
-
return webfeatures
|
|
99
|
-
|
|
100
|
-
def class_for_name(class_full_name:str):
|
|
101
|
-
"""
|
|
102
|
-
クラス名からクラスを取得します。
|
|
103
|
-
|
|
104
|
-
Args:
|
|
105
|
-
class_full_name (str): クラスフルパス名
|
|
106
|
-
|
|
107
|
-
Returns:
|
|
108
|
-
[type]: クラス
|
|
109
|
-
"""
|
|
110
|
-
package_name = '.'.join(class_full_name.split('.')[:-1])
|
|
111
|
-
class_name = class_full_name.split('.')[-1]
|
|
112
|
-
m = importlib.import_module(package_name)
|
|
113
|
-
c = getattr(m, class_name)
|
|
1
|
+
from cmdbox.app import feature
|
|
2
|
+
from typing import List, Dict, Any
|
|
3
|
+
import importlib.util
|
|
4
|
+
import inspect
|
|
5
|
+
import logging
|
|
6
|
+
import pkgutil
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_module_list(package_name) -> List[str]:
|
|
10
|
+
"""
|
|
11
|
+
パッケージ内のモジュール名のリストを取得します。
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
package_name (str): パッケージ名
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
List[str]: モジュール名のリスト
|
|
18
|
+
"""
|
|
19
|
+
package = __import__(package_name, fromlist=[''])
|
|
20
|
+
return [name for _, name, _ in pkgutil.iter_modules(package.__path__)]
|
|
21
|
+
|
|
22
|
+
def load_features(package_name:str, prefix:str="cmdbox_", excludes:list=[], appcls=None, ver=None) -> Dict[str, Any]:
|
|
23
|
+
"""
|
|
24
|
+
フィーチャーを読み込みます。
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
package_name (str): パッケージ名
|
|
28
|
+
prefix (str, optional): プレフィックス. Defaults to "cmdbox_".
|
|
29
|
+
excludes (list, optional): 除外するモジュール名のリスト. Defaults to [].
|
|
30
|
+
appcls ([type], optional): アプリケーションクラス. Defaults to None.
|
|
31
|
+
ver ([type], optional): バージョンモジュール. Defaults to None.
|
|
32
|
+
Returns:
|
|
33
|
+
Dict[str, Any]: フィーチャーのリスト
|
|
34
|
+
"""
|
|
35
|
+
features = dict()
|
|
36
|
+
package = __import__(package_name, fromlist=[''])
|
|
37
|
+
for finder, name, ispkg in pkgutil.iter_modules(package.__path__):
|
|
38
|
+
if name.startswith(prefix):
|
|
39
|
+
if name in excludes:
|
|
40
|
+
continue
|
|
41
|
+
mod = importlib.import_module(f"{package_name}.{name}")
|
|
42
|
+
members = inspect.getmembers(mod, inspect.isclass)
|
|
43
|
+
for name, cls in members:
|
|
44
|
+
if cls is feature.Feature or not issubclass(cls, feature.Feature):
|
|
45
|
+
continue
|
|
46
|
+
fobj = cls(appcls, ver)
|
|
47
|
+
mode = fobj.get_mode()
|
|
48
|
+
if type(mode) is str:
|
|
49
|
+
cmd = fobj.get_cmd()
|
|
50
|
+
if mode not in features:
|
|
51
|
+
features[mode] = dict()
|
|
52
|
+
features[mode][cmd] = fobj.get_option()
|
|
53
|
+
if features[mode][cmd] is None:
|
|
54
|
+
raise ValueError(f'load_features: Cannot get options from {fobj}. The get_option() method returns None.')
|
|
55
|
+
features[mode][cmd]['feature'] = fobj
|
|
56
|
+
elif type(mode) is list:
|
|
57
|
+
for m in mode:
|
|
58
|
+
cmd = fobj.get_cmd()
|
|
59
|
+
if m not in features:
|
|
60
|
+
features[m] = dict()
|
|
61
|
+
features[m][cmd] = fobj.get_option()
|
|
62
|
+
if features[m][cmd] is None:
|
|
63
|
+
raise ValueError(f'load_features: Cannot get options from {fobj}. The get_option() method returns None.')
|
|
64
|
+
features[m][cmd]['feature'] = fobj
|
|
65
|
+
return features
|
|
66
|
+
|
|
67
|
+
def load_webfeatures(package_name:str, prefix:str="cmdbox_web_", excludes:list=[], appcls=None, ver=None, logger:logging.Logger=None) -> List[Any]:
|
|
68
|
+
"""
|
|
69
|
+
Webフィーチャーを読み込みます。
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
package_name (str): パッケージ名
|
|
73
|
+
prefix (str, optional): プレフィックス. Defaults to "cmdbox_web_".
|
|
74
|
+
excludes (list, optional): 除外するモジュール名のリスト. Defaults to [].
|
|
75
|
+
appcls ([type], optional): アプリケーションクラス. Defaults to None.
|
|
76
|
+
ver ([type], optional): バージョンモジュール. Defaults to None.
|
|
77
|
+
logger ([type], optional): ロガー. Defaults to None.
|
|
78
|
+
Returns:
|
|
79
|
+
Dict[feature.WebFeature]: Webフィーチャーのリスト
|
|
80
|
+
"""
|
|
81
|
+
webfeatures = list()
|
|
82
|
+
package = __import__(package_name, fromlist=[''])
|
|
83
|
+
for finder, name, ispkg in pkgutil.iter_modules(package.__path__):
|
|
84
|
+
if name.startswith(prefix):
|
|
85
|
+
if name in excludes:
|
|
86
|
+
if logger.level == logging.DEBUG:
|
|
87
|
+
logger.warning(f'load_webfeatures: {name} is excludes feature.')
|
|
88
|
+
continue
|
|
89
|
+
mod = importlib.import_module(f"{package_name}.{name}")
|
|
90
|
+
members = inspect.getmembers(mod, inspect.isclass)
|
|
91
|
+
for name, cls in members:
|
|
92
|
+
if cls is feature.WebFeature or not issubclass(cls, feature.WebFeature):
|
|
93
|
+
continue
|
|
94
|
+
fobj = cls(appcls, ver)
|
|
95
|
+
if logger is not None and logger.level == logging.DEBUG:
|
|
96
|
+
logger.debug(f'load_webfeatures: {fobj}')
|
|
97
|
+
webfeatures.append(fobj)
|
|
98
|
+
return webfeatures
|
|
99
|
+
|
|
100
|
+
def class_for_name(class_full_name:str):
|
|
101
|
+
"""
|
|
102
|
+
クラス名からクラスを取得します。
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
class_full_name (str): クラスフルパス名
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
[type]: クラス
|
|
109
|
+
"""
|
|
110
|
+
package_name = '.'.join(class_full_name.split('.')[:-1])
|
|
111
|
+
class_name = class_full_name.split('.')[-1]
|
|
112
|
+
m = importlib.import_module(package_name)
|
|
113
|
+
c = getattr(m, class_name)
|
|
114
114
|
return c
|
|
@@ -7,6 +7,7 @@ import logging
|
|
|
7
7
|
import json
|
|
8
8
|
import redis
|
|
9
9
|
import time
|
|
10
|
+
import threading
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
class RedisClient(object):
|
|
@@ -111,9 +112,11 @@ class RedisClient(object):
|
|
|
111
112
|
except KeyboardInterrupt as e:
|
|
112
113
|
return False
|
|
113
114
|
|
|
114
|
-
def send_cmd(self, cmd:str, params:List[str], retry_count:int=20, retry_interval:int=5, outstatus:bool=False, timeout:int =
|
|
115
|
+
def send_cmd(self, cmd:str, params:List[str], retry_count:int=20, retry_interval:int=5, outstatus:bool=False, timeout:int=60, nowait:bool=False):
|
|
115
116
|
"""
|
|
116
|
-
コマンドをRedis
|
|
117
|
+
コマンドをRedisサーバーに送信し、応答を取得します。
|
|
118
|
+
nowait=Trueの場合は、応答を待たずにスレッドで実行します。
|
|
119
|
+
その場合、応答は受信できません。
|
|
117
120
|
|
|
118
121
|
Args:
|
|
119
122
|
cmd (str): コマンド
|
|
@@ -122,37 +125,45 @@ class RedisClient(object):
|
|
|
122
125
|
retry_interval (int, optional): リトライ間隔. Defaults to 5.
|
|
123
126
|
outstatus (bool, optional): ステータスを出力する. Defaults to False.
|
|
124
127
|
timeout (int, optional): タイムアウト時間. Defaults to 60.
|
|
128
|
+
nowait (bool, optional): 応答を待たない. Defaults to False.
|
|
125
129
|
|
|
126
130
|
Returns:
|
|
127
131
|
dict: Redisサーバーからの応答
|
|
128
132
|
"""
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
133
|
+
def send(nowait:bool=False):
|
|
134
|
+
try:
|
|
135
|
+
if timeout <= 0:
|
|
136
|
+
raise ValueError(f"timeout must be greater than 0. timeout={timeout}")
|
|
137
|
+
sreqtime = time.perf_counter()
|
|
138
|
+
if not self.check_server(find_svname=True, retry_count=retry_count, retry_interval=retry_interval, outstatus=outstatus):
|
|
139
|
+
return dict(error=f"Connected server failed or server not found. svname={self.svname.split('-')[1]}")
|
|
140
|
+
reskey = common.random_string()
|
|
141
|
+
reskey = f"cl-{reskey}-{int(time.time())}"
|
|
142
|
+
self.redis_cli.rpush(self.svname, f"{cmd} {reskey} {' '.join([str(p) for p in params])}")
|
|
143
|
+
if nowait: return
|
|
144
|
+
self.is_running = True
|
|
145
|
+
stime = time.time()
|
|
146
|
+
while self.is_running:
|
|
147
|
+
ctime = time.time()
|
|
148
|
+
if ctime - stime > timeout:
|
|
149
|
+
raise Exception(f"Response timed out.")
|
|
150
|
+
res = self.redis_cli.lpop(reskey)
|
|
151
|
+
if res is None or len(res) <= 0:
|
|
152
|
+
time.sleep(0.001)
|
|
153
|
+
continue
|
|
154
|
+
return self._res_cmd(reskey, res, sreqtime)
|
|
155
|
+
raise KeyboardInterrupt(f"Stop command.")
|
|
156
|
+
except KeyboardInterrupt as e:
|
|
157
|
+
self.logger.warning(f"Stop command. cmd={cmd}", exc_info=True)
|
|
158
|
+
return dict(error=f"Stop command. cmd={cmd}")
|
|
159
|
+
except Exception as e:
|
|
160
|
+
self.logger.warning(f"fail to execute command. cmd={cmd}, msg={e}", exc_info=True)
|
|
161
|
+
return dict(error=f"fail to execute command. cmd={cmd}, msg={e}")
|
|
162
|
+
if not nowait:
|
|
163
|
+
return send(nowait)
|
|
164
|
+
else:
|
|
165
|
+
thread = threading.Thread(target=send, args=(nowait,))
|
|
166
|
+
thread.start()
|
|
156
167
|
|
|
157
168
|
def _res_cmd(self, reskey:str, res_msg:bytes, sreqtime:float):
|
|
158
169
|
"""
|
cmdbox/app/edge.py
CHANGED
|
@@ -183,10 +183,10 @@ class Edge(object):
|
|
|
183
183
|
self.svcert_no_verify = opt['svcert_no_verify']
|
|
184
184
|
if self.svcert_no_verify:
|
|
185
185
|
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
|
186
|
-
status, msg = self.signin(opt
|
|
187
|
-
opt
|
|
188
|
-
opt
|
|
189
|
-
int(opt
|
|
186
|
+
status, msg = self.signin(opt.get('auth_type'), opt.get('user'), opt.get('password'), opt.get('apikey'),
|
|
187
|
+
opt.get('oauth2'), int(opt.get('oauth2_port')),
|
|
188
|
+
opt.get('oauth2_tenant_id'), opt.get('oauth2_client_id'), opt.get('oauth2_client_secret'),
|
|
189
|
+
int(opt.get('oauth2_timeout')))
|
|
190
190
|
if status != 0:
|
|
191
191
|
return msg
|
|
192
192
|
|