cmdbox 0.5.1.2__py3-none-any.whl → 0.5.3__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 (143) hide show
  1. cmdbox/app/app.py +4 -2
  2. cmdbox/app/auth/signin.py +634 -631
  3. cmdbox/app/client.py +10 -10
  4. cmdbox/app/common.py +50 -6
  5. cmdbox/app/commons/convert.py +9 -0
  6. cmdbox/app/commons/module.py +113 -113
  7. cmdbox/app/commons/redis_client.py +40 -29
  8. cmdbox/app/edge.py +4 -4
  9. cmdbox/app/features/cli/audit_base.py +138 -0
  10. cmdbox/app/features/cli/cmdbox_audit_createdb.py +224 -0
  11. cmdbox/app/features/cli/cmdbox_audit_delete.py +308 -0
  12. cmdbox/app/features/cli/cmdbox_audit_search.py +416 -0
  13. cmdbox/app/features/cli/cmdbox_audit_write.py +247 -0
  14. cmdbox/app/features/cli/cmdbox_client_file_copy.py +207 -207
  15. cmdbox/app/features/cli/cmdbox_client_file_download.py +207 -207
  16. cmdbox/app/features/cli/cmdbox_client_file_list.py +193 -193
  17. cmdbox/app/features/cli/cmdbox_client_file_mkdir.py +191 -191
  18. cmdbox/app/features/cli/cmdbox_client_file_move.py +199 -199
  19. cmdbox/app/features/cli/cmdbox_client_file_remove.py +190 -190
  20. cmdbox/app/features/cli/cmdbox_client_file_rmdir.py +190 -190
  21. cmdbox/app/features/cli/cmdbox_client_file_upload.py +212 -212
  22. cmdbox/app/features/cli/cmdbox_client_server_info.py +166 -166
  23. cmdbox/app/features/cli/cmdbox_server_list.py +88 -88
  24. cmdbox/app/features/cli/cmdbox_server_stop.py +138 -138
  25. cmdbox/app/features/web/cmdbox_web_audit.py +81 -0
  26. cmdbox/app/features/web/cmdbox_web_audit_metrics.py +72 -0
  27. cmdbox/app/features/web/cmdbox_web_del_cmd.py +2 -0
  28. cmdbox/app/features/web/cmdbox_web_del_pipe.py +1 -0
  29. cmdbox/app/features/web/cmdbox_web_do_signin.py +12 -2
  30. cmdbox/app/features/web/cmdbox_web_do_signout.py +1 -0
  31. cmdbox/app/features/web/cmdbox_web_exec_cmd.py +31 -2
  32. cmdbox/app/features/web/cmdbox_web_exec_pipe.py +1 -0
  33. cmdbox/app/features/web/cmdbox_web_filer download.py +43 -42
  34. cmdbox/app/features/web/cmdbox_web_filer.py +1 -0
  35. cmdbox/app/features/web/cmdbox_web_filer_upload.py +65 -64
  36. cmdbox/app/features/web/cmdbox_web_gui.py +166 -165
  37. cmdbox/app/features/web/cmdbox_web_load_pin.py +43 -43
  38. cmdbox/app/features/web/cmdbox_web_raw_pipe.py +87 -87
  39. cmdbox/app/features/web/cmdbox_web_save_cmd.py +1 -0
  40. cmdbox/app/features/web/cmdbox_web_save_pin.py +42 -42
  41. cmdbox/app/features/web/cmdbox_web_save_pipe.py +1 -0
  42. cmdbox/app/features/web/cmdbox_web_user_data.py +58 -0
  43. cmdbox/app/features/web/cmdbox_web_users.py +12 -0
  44. cmdbox/app/options.py +788 -601
  45. cmdbox/app/web.py +7 -1
  46. cmdbox/extensions/features.yml +23 -0
  47. cmdbox/extensions/sample_project/sample/app/features/cli/sample_client_time.py +82 -82
  48. cmdbox/extensions/sample_project/sample/app/features/cli/sample_server_time.py +145 -145
  49. cmdbox/extensions/user_list.yml +5 -0
  50. cmdbox/licenses/{LICENSE.Sphinx.8.1.3(BSD License).txt → LICENSE.Sphinx.8.2.3(UNKNOWN).txt} +1 -1
  51. cmdbox/licenses/LICENSE.argcomplete.3.6.2(Apache Software License).txt +177 -0
  52. cmdbox/licenses/{LICENSE.babel.2.16.0(BSD License).txt → LICENSE.babel.2.17.0(BSD License).txt } +1 -1
  53. cmdbox/licenses/{LICENSE.pkginfo.1.10.0(MIT License).txt → LICENSE.charset-normalizer.3.4.1(MIT License).txt } +1 -1
  54. cmdbox/licenses/LICENSE.gevent.25.4.1(MIT).txt +25 -0
  55. cmdbox/licenses/LICENSE.greenlet.3.2.0(MIT AND Python-2.0).txt +30 -0
  56. cmdbox/licenses/LICENSE.gunicorn.23.0.0(MIT License).txt +23 -0
  57. cmdbox/licenses/LICENSE.importlib_metadata.8.6.1(Apache Software License).txt +202 -0
  58. cmdbox/licenses/LICENSE.nh3.0.2.21(MIT).txt +21 -0
  59. cmdbox/licenses/{LICENSE.pillow.11.0.0(CMU License (MIT-CMU)).txt → LICENSE.pillow.11.1.0(CMU License (MIT-CMU)).txt } +27 -40
  60. cmdbox/licenses/LICENSE.pillow.11.2.1(UNKNOWN).txt +1200 -0
  61. cmdbox/licenses/LICENSE.plyer.2.1.0(MIT License).txt +19 -0
  62. cmdbox/licenses/LICENSE.prompt_toolkit.3.0.50(BSD License).txt +27 -0
  63. cmdbox/licenses/LICENSE.prompt_toolkit.3.0.51(BSD License).txt +27 -0
  64. cmdbox/licenses/LICENSE.psycopg-binary.3.2.6(GNU Lesser General Public License v3 (LGPLv3)).txt +165 -0
  65. cmdbox/licenses/LICENSE.psycopg-pool.3.2.6(GNU Lesser General Public License v3 (LGPLv3)).txt +165 -0
  66. cmdbox/licenses/LICENSE.psycopg.3.2.6(GNU Lesser General Public License v3 (LGPLv3)).txt +165 -0
  67. cmdbox/licenses/LICENSE.pycryptodome.3.22.0(BSD License; Public Domain).txt +61 -0
  68. cmdbox/licenses/LICENSE.pydantic.2.11.3(MIT License).txt +21 -0
  69. cmdbox/licenses/LICENSE.pydantic_core.2.33.1(MIT License).txt +21 -0
  70. cmdbox/licenses/LICENSE.pystray.0.19.5(GNU Lesser General Public License v3 (LGPLv3)).txt +674 -0
  71. cmdbox/licenses/LICENSE.questionary.2.1.0(MIT License).txt +19 -0
  72. 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
  73. cmdbox/licenses/{LICENSE.six.1.16.0(MIT License).txt → LICENSE.six.1.17.0(MIT License).txt } +1 -1
  74. cmdbox/licenses/LICENSE.starlette.0.46.2(BSD License).txt +27 -0
  75. cmdbox/licenses/{LICENSE.charset-normalizer.3.4.0(MIT License).txt → LICENSE.typing-inspection.0.4.0(MIT License).txt } +2 -2
  76. cmdbox/licenses/LICENSE.typing_extensions.4.13.2(UNKNOWN).txt +279 -0
  77. cmdbox/licenses/LICENSE.tzdata.2025.2(Apache Software License).txt +15 -0
  78. cmdbox/licenses/LICENSE.urllib3.2.4.0(UNKNOWN).txt +21 -0
  79. cmdbox/licenses/LICENSE.uvicorn.0.34.1(BSD License).txt +27 -0
  80. cmdbox/licenses/LICENSE.watchfiles.1.0.5(MIT License).txt +21 -0
  81. cmdbox/licenses/files.txt +49 -38
  82. cmdbox/logconf_audit.yml +30 -0
  83. cmdbox/logconf_cmdbox.yml +30 -0
  84. cmdbox/version.py +2 -2
  85. cmdbox/web/assets/apexcharts/apexcharts.css +679 -0
  86. cmdbox/web/assets/apexcharts/apexcharts.min.js +38 -0
  87. cmdbox/web/assets/cmdbox/audit.js +340 -0
  88. cmdbox/web/assets/cmdbox/color_mode.css +520 -0
  89. cmdbox/web/assets/cmdbox/common.js +416 -24
  90. cmdbox/web/assets/cmdbox/filer_modal.js +1 -1
  91. cmdbox/web/assets/cmdbox/list_cmd.js +10 -275
  92. cmdbox/web/assets/cmdbox/list_pipe.js +3 -3
  93. cmdbox/web/assets/cmdbox/main.js +2 -2
  94. cmdbox/web/assets/cmdbox/result.js +2 -2
  95. cmdbox/web/assets/cmdbox/signin.js +2 -2
  96. cmdbox/web/assets/cmdbox/users.js +19 -20
  97. cmdbox/web/assets/cmdbox/view_raw.js +1 -1
  98. cmdbox/web/assets/cmdbox/view_result.js +11 -13
  99. cmdbox/web/assets/filer/filer.js +2 -2
  100. cmdbox/web/assets/filer/main.js +2 -2
  101. cmdbox/web/assets_license_list.txt +4 -1
  102. cmdbox/web/audit.html +268 -0
  103. cmdbox/web/filer.html +37 -12
  104. cmdbox/web/gui.html +36 -53
  105. cmdbox/web/result.html +24 -3
  106. cmdbox/web/signin.html +35 -14
  107. cmdbox/web/users.html +21 -3
  108. {cmdbox-0.5.1.2.dist-info → cmdbox-0.5.3.dist-info}/METADATA +28 -5
  109. {cmdbox-0.5.1.2.dist-info → cmdbox-0.5.3.dist-info}/RECORD +142 -103
  110. {cmdbox-0.5.1.2.dist-info → cmdbox-0.5.3.dist-info}/entry_points.txt +0 -1
  111. cmdbox/licenses/LICENSE.nh3.0.2.18(MIT).txt +0 -1
  112. /cmdbox/licenses/{LICENSE.Jinja2.3.1.4(BSD License).txt → LICENSE.Jinja2.3.1.6(BSD License).txt} +0 -0
  113. /cmdbox/licenses/{LICENSE.Pygments.2.18.0(BSD License).txt → LICENSE.Pygments.2.19.1(BSD License).txt} +0 -0
  114. /cmdbox/licenses/{LICENSE.anyio.4.6.2.post1(MIT License).txt → LICENSE.anyio.4.9.0(MIT License).txt} +0 -0
  115. /cmdbox/licenses/{LICENSE.argcomplete.3.5.1(Apache Software License).txt → LICENSE.argcomplete.3.6.1(Apache Software License).txt} +0 -0
  116. /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
  117. /cmdbox/licenses/{LICENSE.click.8.1.7(BSD License).txt → LICENSE.click.8.1.8(BSD License).txt} +0 -0
  118. /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
  119. /cmdbox/licenses/{LICENSE.fastapi.0.115.5(MIT License).txt → LICENSE.fastapi.0.115.12(MIT License).txt} +0 -0
  120. /cmdbox/licenses/{LICENSE.importlib_metadata.8.5.0(Apache Software License).txt → LICENSE.id.1.5.0(Apache Software License).txt} +0 -0
  121. /cmdbox/licenses/{LICENSE.keyring.25.5.0(MIT License).txt → LICENSE.keyring.25.6.0(MIT License).txt} +0 -0
  122. /cmdbox/licenses/{LICENSE.more-itertools.10.5.0(MIT License).txt → LICENSE.more-itertools.10.6.0(MIT License).txt} +0 -0
  123. /cmdbox/licenses/{LICENSE.numpy.2.1.3(BSD License).txt → LICENSE.numpy.2.2.4(BSD License).txt} +0 -0
  124. /cmdbox/licenses/{LICENSE.prettytable.3.12.0(BSD License).txt → LICENSE.prettytable.3.16.0(UNKNOWN).txt} +0 -0
  125. /cmdbox/licenses/{LICENSE.pydantic.2.10.2(MIT License).txt → LICENSE.pydantic.2.11.1(MIT License).txt} +0 -0
  126. /cmdbox/licenses/{LICENSE.pydantic_core.2.27.1(MIT License).txt → LICENSE.pydantic_core.2.33.0(MIT License).txt} +0 -0
  127. /cmdbox/licenses/{LICENSE.python-dotenv.1.0.1(BSD License).txt → LICENSE.python-dotenv.1.1.0(BSD License).txt} +0 -0
  128. /cmdbox/licenses/{LICENSE.python-multipart.0.0.17(Apache Software License).txt → LICENSE.python-multipart.0.0.20(Apache Software License).txt} +0 -0
  129. /cmdbox/licenses/{LICENSE.redis.5.2.0(MIT License).txt → LICENSE.redis.5.2.1(MIT License).txt} +0 -0
  130. /cmdbox/licenses/{LICENSE.rich.13.9.4(MIT License).txt → LICENSE.rich.14.0.0(MIT License).txt} +0 -0
  131. /cmdbox/licenses/{LICENSE.sphinx-intl.2.3.0(BSD License).txt → LICENSE.sphinx-intl.2.3.1(BSD License).txt} +0 -0
  132. /cmdbox/licenses/{LICENSE.starlette.0.41.3(BSD License).txt → LICENSE.starlette.0.46.1(BSD License).txt} +0 -0
  133. /cmdbox/licenses/{LICENSE.tomli.2.1.0(MIT License).txt → LICENSE.tomli.2.2.1(MIT License).txt} +0 -0
  134. /cmdbox/licenses/{LICENSE.twine.5.1.1(Apache Software License).txt → LICENSE.twine.6.1.0(Apache Software License).txt} +0 -0
  135. /cmdbox/licenses/{LICENSE.typing_extensions.4.12.2(Python Software Foundation License).txt → LICENSE.typing_extensions.4.13.0(UNKNOWN).txt} +0 -0
  136. /cmdbox/licenses/{LICENSE.urllib3.2.2.3(MIT License).txt → LICENSE.urllib3.2.3.0(MIT License).txt} +0 -0
  137. /cmdbox/licenses/{LICENSE.uvicorn.0.32.1(BSD License).txt → LICENSE.uvicorn.0.34.0(BSD License).txt} +0 -0
  138. /cmdbox/licenses/{LICENSE.watchfiles.1.0.0(MIT License).txt → LICENSE.watchfiles.1.0.4(MIT License).txt} +0 -0
  139. /cmdbox/licenses/{LICENSE.websockets.14.1(BSD License).txt → LICENSE.websockets.15.0.1(BSD License).txt} +0 -0
  140. /cmdbox/licenses/{LICENSE.zope.interface.7.1.1(Zope Public License).txt → LICENSE.zope.interface.7.2(Zope Public License).txt} +0 -0
  141. {cmdbox-0.5.1.2.dist-info → cmdbox-0.5.3.dist-info}/LICENSE +0 -0
  142. {cmdbox-0.5.1.2.dist-info → cmdbox-0.5.3.dist-info}/WHEEL +0 -0
  143. {cmdbox-0.5.1.2.dist-info → cmdbox-0.5.3.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 = 60):
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 = 60):
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 = 60):
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 = 60):
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 = 60):
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 = 60):
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 = 60):
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 = 60):
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 = 60):
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 = 60):
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
- dst = Path(data) / '.samples' if data is not None else HOME_DIR / '.samples'
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, dst, dirs_exist_ok=True, copy_function=copy)
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
- return o.strftime('%Y-%m-%dT%H:%M:%S')
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']) == dict:
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}"
@@ -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')
@@ -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 = 60):
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
- try:
130
- if timeout <= 0:
131
- raise ValueError(f"timeout must be greater than 0. timeout={timeout}")
132
- sreqtime = time.perf_counter()
133
- if not self.check_server(find_svname=True, retry_count=retry_count, retry_interval=retry_interval, outstatus=outstatus):
134
- return dict(error=f"Connected server failed or server not found. svname={self.svname.split('-')[1]}")
135
- reskey = common.random_string()
136
- reskey = f"cl-{reskey}-{int(time.time())}"
137
- self.redis_cli.rpush(self.svname, f"{cmd} {reskey} {' '.join([str(p) for p in params])}")
138
- self.is_running = True
139
- stime = time.time()
140
- while self.is_running:
141
- ctime = time.time()
142
- if ctime - stime > timeout:
143
- raise Exception(f"Response timed out.")
144
- res = self.redis_cli.lpop(reskey)
145
- if res is None or len(res) <= 0:
146
- time.sleep(0.001)
147
- continue
148
- return self._res_cmd(reskey, res, sreqtime)
149
- raise KeyboardInterrupt(f"Stop command.")
150
- except KeyboardInterrupt as e:
151
- self.logger.warning(f"Stop command. cmd={cmd}", exc_info=True)
152
- return dict(error=f"Stop command. cmd={cmd}")
153
- except Exception as e:
154
- self.logger.warning(f"fail to execute command. cmd={cmd}, msg={e}", exc_info=True)
155
- return dict(error=f"fail to execute command. cmd={cmd}, msg={e}")
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['auth_type'], opt['user'], opt['password'], opt['apikey'],
187
- opt['oauth2'], int(opt['oauth2_port']),
188
- opt['oauth2_tenant_id'], opt['oauth2_client_id'], opt['oauth2_client_secret'],
189
- int(opt['oauth2_timeout']))
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