hcs-core 0.1.283__py3-none-any.whl → 0.1.316__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.
- hcs_core/__init__.py +1 -1
- hcs_core/ctxp/_init.py +8 -3
- hcs_core/ctxp/built_in_cmds/context.py +15 -1
- hcs_core/ctxp/built_in_cmds/profile.py +20 -12
- hcs_core/ctxp/cli_options.py +28 -8
- hcs_core/ctxp/cli_processor.py +23 -12
- hcs_core/ctxp/context.py +2 -0
- hcs_core/ctxp/data_util.py +35 -15
- hcs_core/ctxp/duration.py +1 -3
- hcs_core/ctxp/fstore.py +1 -1
- hcs_core/ctxp/jsondot.py +1 -1
- hcs_core/ctxp/logger.py +4 -4
- hcs_core/ctxp/profile.py +6 -7
- hcs_core/ctxp/recent.py +3 -3
- hcs_core/ctxp/state.py +2 -2
- hcs_core/ctxp/task_schd.py +0 -2
- hcs_core/ctxp/telemetry.py +145 -0
- hcs_core/ctxp/util.py +158 -25
- hcs_core/plan/__init__.py +1 -0
- hcs_core/plan/core.py +20 -17
- hcs_core/plan/dag.py +7 -8
- hcs_core/plan/kop.py +19 -7
- hcs_core/sglib/auth.py +111 -98
- hcs_core/sglib/cli_options.py +15 -1
- hcs_core/sglib/client_util.py +173 -75
- hcs_core/sglib/csp.py +71 -5
- hcs_core/sglib/ez_client.py +48 -32
- hcs_core/sglib/hcs_client.py +2 -6
- hcs_core/sglib/login_support.py +17 -9
- hcs_core/sglib/utils.py +4 -1
- hcs_core/util/check_license.py +0 -2
- hcs_core/util/job_view.py +28 -10
- hcs_core/util/query_util.py +17 -8
- hcs_core/util/versions.py +12 -10
- {hcs_core-0.1.283.dist-info → hcs_core-0.1.316.dist-info}/METADATA +19 -17
- hcs_core-0.1.316.dist-info/RECORD +69 -0
- {hcs_core-0.1.283.dist-info → hcs_core-0.1.316.dist-info}/WHEEL +1 -1
- hcs_core-0.1.283.dist-info/RECORD +0 -68
hcs_core/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.1.
|
|
1
|
+
__version__ = "0.1.316"
|
hcs_core/ctxp/_init.py
CHANGED
|
@@ -19,7 +19,7 @@ from pathlib import Path
|
|
|
19
19
|
|
|
20
20
|
import click
|
|
21
21
|
|
|
22
|
-
from . import cli_processor, config, profile, state
|
|
22
|
+
from . import cli_processor, config, profile, state, telemetry
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
def _get_store_path():
|
|
@@ -43,6 +43,7 @@ def init(app_name: str, store_path: str = user_home, config_path: str = "./confi
|
|
|
43
43
|
global _initialized_app_name
|
|
44
44
|
if _initialized_app_name == app_name:
|
|
45
45
|
return
|
|
46
|
+
|
|
46
47
|
if _initialized_app_name is not None:
|
|
47
48
|
raise ValueError(f"App {app_name} already initialized with {_initialized_app_name}")
|
|
48
49
|
_initialized_app_name = app_name
|
|
@@ -79,8 +80,12 @@ def app_name():
|
|
|
79
80
|
|
|
80
81
|
def init_cli(main_cli: click.Group, commands_dir: str = "./cmds"):
|
|
81
82
|
try:
|
|
82
|
-
|
|
83
|
-
|
|
83
|
+
telemetry.start(_initialized_app_name)
|
|
84
|
+
ret = cli_processor.init(main_cli, commands_dir)
|
|
85
|
+
telemetry.end()
|
|
86
|
+
return ret
|
|
87
|
+
except BaseException as e:
|
|
88
|
+
telemetry.end(error=e)
|
|
84
89
|
if _need_stack_trace(e):
|
|
85
90
|
raise e
|
|
86
91
|
else:
|
|
@@ -48,7 +48,7 @@ def get(name: str, key: str):
|
|
|
48
48
|
@click.argument("name")
|
|
49
49
|
@click.argument("key_value") # 'key value pair, example: k1=v1'
|
|
50
50
|
def set(name: str, key_value: str):
|
|
51
|
-
"""Set a context
|
|
51
|
+
"""Set a context property by name."""
|
|
52
52
|
parts = key_value.split("=")
|
|
53
53
|
if len(parts) != 2:
|
|
54
54
|
ctxp.panic("Invalid KEY_VALUE format. Valid example: key1=value1")
|
|
@@ -58,6 +58,20 @@ def set(name: str, key_value: str):
|
|
|
58
58
|
ctxp.context.set(name, data)
|
|
59
59
|
|
|
60
60
|
|
|
61
|
+
@context.command()
|
|
62
|
+
@click.argument("name")
|
|
63
|
+
@click.argument("key")
|
|
64
|
+
def unset(name: str, key: str):
|
|
65
|
+
"""Unset a context property by name."""
|
|
66
|
+
data = ctxp.context.get(name, default=None)
|
|
67
|
+
if data is None:
|
|
68
|
+
return
|
|
69
|
+
data.pop(key, None)
|
|
70
|
+
if len(data) == 0:
|
|
71
|
+
return ctxp.context.delete(name)
|
|
72
|
+
return ctxp.context.set(name, data)
|
|
73
|
+
|
|
74
|
+
|
|
61
75
|
@context.command()
|
|
62
76
|
@click.argument("name")
|
|
63
77
|
def delete(name: str):
|
|
@@ -19,7 +19,7 @@ import sys
|
|
|
19
19
|
import click
|
|
20
20
|
import questionary
|
|
21
21
|
|
|
22
|
-
from hcs_core.ctxp import cli_processor, panic, profile, util
|
|
22
|
+
from hcs_core.ctxp import cli_options, cli_processor, panic, profile, util
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
@click.group(name="profile", cls=cli_processor.LazyGroup)
|
|
@@ -53,7 +53,7 @@ def use(name: str):
|
|
|
53
53
|
if ret:
|
|
54
54
|
if profile.use(ret) is None:
|
|
55
55
|
panic("No such profile: " + name)
|
|
56
|
-
return
|
|
56
|
+
return profile.current()
|
|
57
57
|
else:
|
|
58
58
|
# aborted
|
|
59
59
|
return "", 1
|
|
@@ -62,8 +62,8 @@ def use(name: str):
|
|
|
62
62
|
@profile_cmd_group.command()
|
|
63
63
|
@click.option("--from-name", "-f", required=False)
|
|
64
64
|
@click.option("--to-name", "-t", required=True)
|
|
65
|
-
@click.option("--edit", "-e", required=False, is_flag=True, help="Edit the profile after copying.")
|
|
66
|
-
def copy(from_name: str, to_name: str,
|
|
65
|
+
@click.option("--no-edit", "-e", required=False, is_flag=True, help="Edit the profile after copying.")
|
|
66
|
+
def copy(from_name: str, to_name: str, no_edit: bool):
|
|
67
67
|
"""Copy profile."""
|
|
68
68
|
|
|
69
69
|
if not from_name:
|
|
@@ -76,10 +76,9 @@ def copy(from_name: str, to_name: str, edit: bool):
|
|
|
76
76
|
panic("Profile already exists: " + to_name)
|
|
77
77
|
data = profile.create(to_name, data, True)
|
|
78
78
|
|
|
79
|
-
if
|
|
80
|
-
util.launch_text_editor(profile.file(to_name))
|
|
81
|
-
else:
|
|
79
|
+
if no_edit:
|
|
82
80
|
return data
|
|
81
|
+
util.launch_text_editor(profile.file(to_name))
|
|
83
82
|
|
|
84
83
|
|
|
85
84
|
@profile_cmd_group.command()
|
|
@@ -89,9 +88,7 @@ def get(name: str):
|
|
|
89
88
|
if name:
|
|
90
89
|
data = profile.get(name)
|
|
91
90
|
if data is None:
|
|
92
|
-
panic(
|
|
93
|
-
"Profile not found. Use 'hcs profile list' to show available profiles, or 'hcs profile init' to create one."
|
|
94
|
-
)
|
|
91
|
+
panic("Profile not found. Use 'hcs profile list' to show available profiles, or 'hcs profile init' to create one.")
|
|
95
92
|
else:
|
|
96
93
|
data = profile.current()
|
|
97
94
|
if data is None:
|
|
@@ -103,9 +100,20 @@ def get(name: str):
|
|
|
103
100
|
|
|
104
101
|
|
|
105
102
|
@profile_cmd_group.command()
|
|
106
|
-
@
|
|
107
|
-
|
|
103
|
+
@cli_options.confirm
|
|
104
|
+
@click.argument("name", required=False)
|
|
105
|
+
def delete(confirm: bool, name: str):
|
|
108
106
|
"""Delete a profile by name."""
|
|
107
|
+
if not name:
|
|
108
|
+
name = profile.name()
|
|
109
|
+
|
|
110
|
+
if not profile.exists(name):
|
|
111
|
+
return
|
|
112
|
+
if not confirm:
|
|
113
|
+
question = f"Are you sure to delete profile '{name}'?"
|
|
114
|
+
if not questionary.confirm(question, default=False).ask():
|
|
115
|
+
click.echo("Aborted")
|
|
116
|
+
return
|
|
109
117
|
profile.delete(name)
|
|
110
118
|
|
|
111
119
|
|
hcs_core/ctxp/cli_options.py
CHANGED
|
@@ -13,6 +13,8 @@ See the License for the specific language governing permissions and
|
|
|
13
13
|
limitations under the License.
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
|
+
import os
|
|
17
|
+
|
|
16
18
|
import click
|
|
17
19
|
|
|
18
20
|
verbose = click.option(
|
|
@@ -27,7 +29,7 @@ verbose = click.option(
|
|
|
27
29
|
output = click.option(
|
|
28
30
|
"--output",
|
|
29
31
|
"-o",
|
|
30
|
-
type=click.Choice(["json", "json-compact", "yaml", "
|
|
32
|
+
type=click.Choice(["json", "json-compact", "yaml", "yml", "table", "t", "text"], case_sensitive=False),
|
|
31
33
|
default=None,
|
|
32
34
|
hidden=True,
|
|
33
35
|
help="Specify output format",
|
|
@@ -41,6 +43,14 @@ field = click.option(
|
|
|
41
43
|
help="Specify fields to output, in comma separated field names.",
|
|
42
44
|
)
|
|
43
45
|
|
|
46
|
+
exclude_field = click.option(
|
|
47
|
+
"--exclude-field",
|
|
48
|
+
type=str,
|
|
49
|
+
required=False,
|
|
50
|
+
hidden=True,
|
|
51
|
+
help="Specify fields to exclude from output, in comma separated field names.",
|
|
52
|
+
)
|
|
53
|
+
|
|
44
54
|
wait = click.option(
|
|
45
55
|
"--wait",
|
|
46
56
|
"-w",
|
|
@@ -66,9 +76,7 @@ sort = click.option(
|
|
|
66
76
|
help="Ascending/Descending. Format is property,{asc|desc} and default is ascending",
|
|
67
77
|
)
|
|
68
78
|
|
|
69
|
-
limit = click.option(
|
|
70
|
-
"--limit", "-l", type=int, required=False, default=100, help="Optionally, specify the number of records to fetch."
|
|
71
|
-
)
|
|
79
|
+
limit = click.option("--limit", "-l", type=int, required=False, default=100, help="Optionally, specify the number of records to fetch.")
|
|
72
80
|
|
|
73
81
|
ids = click.option(
|
|
74
82
|
"--ids",
|
|
@@ -93,14 +101,26 @@ first = click.option(
|
|
|
93
101
|
|
|
94
102
|
force = click.option("--force/--grace", type=bool, default=True, help="Specify deletion mode: forceful, or graceful.")
|
|
95
103
|
|
|
96
|
-
confirm = click.option(
|
|
97
|
-
|
|
104
|
+
confirm = click.option("--confirm/--prompt", "-y", type=bool, default=False, help="Confirm the operation without prompt.")
|
|
105
|
+
|
|
106
|
+
env = click.option(
|
|
107
|
+
"--env",
|
|
108
|
+
multiple=True,
|
|
109
|
+
type=str,
|
|
110
|
+
required=False,
|
|
111
|
+
help="Alternative explicit in-line environment variable override in KEY=VALUE format.",
|
|
98
112
|
)
|
|
99
113
|
|
|
100
114
|
|
|
101
|
-
def
|
|
115
|
+
def apply_env(envs):
|
|
116
|
+
for kv in envs:
|
|
117
|
+
k, v = kv.split("=", 1)
|
|
118
|
+
os.environ[k.strip()] = v.strip()
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def formatter(formatter=None, columns=None):
|
|
102
122
|
def decorator(f):
|
|
103
|
-
f.formatter =
|
|
123
|
+
f.formatter = formatter if formatter else columns
|
|
104
124
|
return f
|
|
105
125
|
|
|
106
126
|
return decorator
|
hcs_core/ctxp/cli_processor.py
CHANGED
|
@@ -24,8 +24,7 @@ from pathlib import Path
|
|
|
24
24
|
import click
|
|
25
25
|
from click.core import Group
|
|
26
26
|
|
|
27
|
-
from .
|
|
28
|
-
from .util import avoid_trace_for_ctrl_c, print_error, print_output, validate_error_return
|
|
27
|
+
from .util import avoid_trace_for_ctrl_c, default_table_formatter, print_error, print_output, validate_error_return
|
|
29
28
|
|
|
30
29
|
_eager_loading = os.environ.get("_CTXP_EAGER_LOAD")
|
|
31
30
|
if _eager_loading:
|
|
@@ -61,6 +60,8 @@ class LazyGroup(click.Group):
|
|
|
61
60
|
|
|
62
61
|
def _ensure_extension(self):
|
|
63
62
|
if self._extension:
|
|
63
|
+
from .extension import ensure_extension
|
|
64
|
+
|
|
64
65
|
ensure_extension(self._extension)
|
|
65
66
|
|
|
66
67
|
|
|
@@ -75,6 +76,7 @@ def _ensure_sub_group(current: Group, mod_path: Path):
|
|
|
75
76
|
meta = _read_group_meta(mod_path)
|
|
76
77
|
help = meta.get("help")
|
|
77
78
|
extension = meta.get("extension")
|
|
79
|
+
hidden = meta.get("hidden", False)
|
|
78
80
|
|
|
79
81
|
subgroup = current.commands.get(name)
|
|
80
82
|
if subgroup and isinstance(subgroup, Group):
|
|
@@ -82,7 +84,7 @@ def _ensure_sub_group(current: Group, mod_path: Path):
|
|
|
82
84
|
subgroup.mod_path = mod_path
|
|
83
85
|
return subgroup
|
|
84
86
|
|
|
85
|
-
subgroup = LazyGroup(extension=extension, name=name, help=help, mod_path=mod_path)
|
|
87
|
+
subgroup = LazyGroup(extension=extension, name=name, help=help, mod_path=mod_path, hidden=hidden)
|
|
86
88
|
current.add_command(subgroup)
|
|
87
89
|
return subgroup
|
|
88
90
|
|
|
@@ -166,32 +168,41 @@ def _import_cmd_file(mod_path: Path, parent: click.core.Group):
|
|
|
166
168
|
# Create a new decorator that combines the individual decorators
|
|
167
169
|
def _default_io(cmd: click.Command):
|
|
168
170
|
|
|
169
|
-
from .cli_options import field, first, ids, output
|
|
171
|
+
from .cli_options import exclude_field, field, first, ids, output
|
|
170
172
|
|
|
171
173
|
cmd = output(cmd)
|
|
172
174
|
# cmd = cli_options.verbose(cmd)
|
|
173
175
|
cmd = field(cmd)
|
|
174
176
|
cmd = ids(cmd)
|
|
175
177
|
cmd = first(cmd)
|
|
178
|
+
cmd = exclude_field(cmd)
|
|
176
179
|
callback = cmd.callback
|
|
177
180
|
|
|
178
181
|
def inner(*args, **kwargs):
|
|
179
182
|
io_args = {
|
|
180
183
|
"output": kwargs.pop("output"),
|
|
181
|
-
#'verbose': kwargs.pop('verbose'),
|
|
184
|
+
# 'verbose': kwargs.pop('verbose'),
|
|
182
185
|
"field": kwargs.pop("field"),
|
|
183
186
|
"ids": kwargs.pop("ids"),
|
|
184
187
|
"first": kwargs.pop("first"),
|
|
188
|
+
"exclude_field": kwargs.pop("exclude_field"),
|
|
185
189
|
}
|
|
186
|
-
|
|
190
|
+
ctx = click.get_current_context()
|
|
191
|
+
from .telemetry import update as telemetry_update
|
|
187
192
|
|
|
188
|
-
|
|
189
|
-
if not hasattr(callback, "formatter"):
|
|
190
|
-
from .util import CtxpException
|
|
193
|
+
telemetry_update(ctx.command_path, ctx.params)
|
|
191
194
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
+
if io_args["output"] == "table" or io_args["output"] == "t":
|
|
196
|
+
|
|
197
|
+
def _format(data):
|
|
198
|
+
if hasattr(callback, "formatter"):
|
|
199
|
+
formatter = callback.__getattribute__("formatter")
|
|
200
|
+
else:
|
|
201
|
+
formatter = default_table_formatter
|
|
202
|
+
if isinstance(formatter, dict):
|
|
203
|
+
return default_table_formatter(data, formatter)
|
|
204
|
+
else:
|
|
205
|
+
return formatter(data)
|
|
195
206
|
|
|
196
207
|
io_args["format"] = _format
|
|
197
208
|
ret = callback(*args, **kwargs)
|
hcs_core/ctxp/context.py
CHANGED
hcs_core/ctxp/data_util.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import json
|
|
2
|
+
import os
|
|
2
3
|
import re
|
|
3
4
|
from io import TextIOWrapper
|
|
4
5
|
from os import chmod, path
|
|
@@ -99,9 +100,7 @@ def strict_dict_to_class(data: dict, class_type):
|
|
|
99
100
|
value = strict_dict_to_class(value, field_type)
|
|
100
101
|
setattr(inst, field_name, value)
|
|
101
102
|
continue
|
|
102
|
-
raise ValueError(
|
|
103
|
-
f"Field '{class_type.__name__}.{field_name}' has an incorrect type. Declared: {field_type}, actual: {type(value)}"
|
|
104
|
-
)
|
|
103
|
+
raise ValueError(f"Field '{class_type.__name__}.{field_name}' has an incorrect type. Declared: {field_type}, actual: {type(value)}")
|
|
105
104
|
return inst
|
|
106
105
|
|
|
107
106
|
|
|
@@ -260,22 +259,26 @@ def process_variables(obj: dict, fn_get_var=None, use_env: bool = True):
|
|
|
260
259
|
fn_get_var = _fn_get_var
|
|
261
260
|
|
|
262
261
|
if use_env:
|
|
263
|
-
import os
|
|
264
262
|
|
|
265
263
|
prev_fn_get_var = fn_get_var
|
|
266
264
|
|
|
267
265
|
def _fn_get_var_from_env(name: str):
|
|
268
266
|
if not name.startswith("env."):
|
|
269
267
|
return prev_fn_get_var(name)
|
|
270
|
-
v = None
|
|
271
268
|
actual_name = name[4:]
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
269
|
+
if actual_name.endswith("?"):
|
|
270
|
+
# optional
|
|
271
|
+
actual_name = actual_name[:-1]
|
|
272
|
+
required = False
|
|
273
|
+
else:
|
|
274
|
+
# required
|
|
275
|
+
required = True
|
|
276
|
+
|
|
277
|
+
if actual_name not in os.environ:
|
|
278
|
+
if required:
|
|
279
|
+
raise CtxpException(f"Environment variable '{actual_name}' is used in template, but not found. ")
|
|
280
|
+
return None, True
|
|
281
|
+
return os.environ[actual_name], True
|
|
279
282
|
|
|
280
283
|
fn_get_var = _fn_get_var_from_env
|
|
281
284
|
|
|
@@ -336,9 +339,7 @@ def resolve_expression(expr, fn_get_value, referencing_attr_path) -> Tuple[Any,
|
|
|
336
339
|
f"Invalid variable value for expression. Expect list, actual {type(target_value).__name__}. attr_path={referencing_attr_path}, src_var_name={src_var_name}"
|
|
337
340
|
)
|
|
338
341
|
if not mapped_value.startswith(tmp_var_name + "."):
|
|
339
|
-
raise CtxpException(
|
|
340
|
-
f"Unsupported expression. attr_path={referencing_attr_path}, src_var_name={src_var_name}"
|
|
341
|
-
)
|
|
342
|
+
raise CtxpException(f"Unsupported expression. attr_path={referencing_attr_path}, src_var_name={src_var_name}")
|
|
342
343
|
new_attr_path = mapped_value[len(tmp_var_name) + 1 :]
|
|
343
344
|
ret = []
|
|
344
345
|
for i in target_value:
|
|
@@ -391,6 +392,25 @@ def get_common_items(iter1, iter2):
|
|
|
391
392
|
return common_items
|
|
392
393
|
|
|
393
394
|
|
|
395
|
+
def get_delta(dict_base, dict_update):
|
|
396
|
+
delta = {}
|
|
397
|
+
for k, v2 in dict_update.items():
|
|
398
|
+
v1 = dict_base.get(k)
|
|
399
|
+
if not deep_equals(v1, v2):
|
|
400
|
+
delta[k] = v2
|
|
401
|
+
return delta
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
def deep_equals(v1, v2):
|
|
405
|
+
if v1 is None and v2 is None:
|
|
406
|
+
return True
|
|
407
|
+
if v1 is None or v2 is None:
|
|
408
|
+
return False
|
|
409
|
+
if v1 == v2:
|
|
410
|
+
return True
|
|
411
|
+
return json.dumps(v1) == json.dumps(v2)
|
|
412
|
+
|
|
413
|
+
|
|
394
414
|
def _evaluate(value, smart_search):
|
|
395
415
|
# If we found an exact match, return 2 immediately
|
|
396
416
|
if value == smart_search:
|
hcs_core/ctxp/duration.py
CHANGED
|
@@ -2,9 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import re
|
|
4
4
|
|
|
5
|
-
PATTERN = (
|
|
6
|
-
"([-+]?)P(?:([-+]?[0-9]+)D)?(T(?:([-+]?[0-9]+)H)?(?:([-+]?[0-9]+)M)?(?:([-+]?[0-9]+)(?:[.,]([0-9]{0,9}))?S)?)?"
|
|
7
|
-
)
|
|
5
|
+
PATTERN = "([-+]?)P(?:([-+]?[0-9]+)D)?(T(?:([-+]?[0-9]+)H)?(?:([-+]?[0-9]+)M)?(?:([-+]?[0-9]+)(?:[.,]([0-9]{0,9}))?S)?)?"
|
|
8
6
|
|
|
9
7
|
# Examples
|
|
10
8
|
|
hcs_core/ctxp/fstore.py
CHANGED
|
@@ -199,7 +199,7 @@ class fstore:
|
|
|
199
199
|
outfile.write(str(data))
|
|
200
200
|
elif format == "json-compact":
|
|
201
201
|
jsondot.save(data=data, file=file_path, pretty=False, lock=True)
|
|
202
|
-
elif format == "yaml":
|
|
202
|
+
elif format == "yaml" or format == "yml":
|
|
203
203
|
import yaml
|
|
204
204
|
|
|
205
205
|
with open(file_path, "w", encoding="utf-8") as outfile:
|
hcs_core/ctxp/jsondot.py
CHANGED
hcs_core/ctxp/logger.py
CHANGED
|
@@ -21,7 +21,9 @@ from logging.handlers import RotatingFileHandler
|
|
|
21
21
|
import coloredlogs
|
|
22
22
|
|
|
23
23
|
LOG_FORMAT_SIMPLE = "%(levelname).4s %(asctime)s %(name)-16s %(message)s"
|
|
24
|
-
LOG_FORMAT_LONG =
|
|
24
|
+
LOG_FORMAT_LONG = (
|
|
25
|
+
"%(color_on)s%(asctime)s.%(msecs)03d [%(process)-5d:%(threadName)-12s] %(levelname)-7s [%(name)-16s] %(message)s%(color_off)s"
|
|
26
|
+
)
|
|
25
27
|
DATE_FORMAT_SIMPLE = "%H:%M:%S"
|
|
26
28
|
|
|
27
29
|
|
|
@@ -133,9 +135,7 @@ def setup(
|
|
|
133
135
|
)
|
|
134
136
|
|
|
135
137
|
|
|
136
|
-
def setup_console_output(
|
|
137
|
-
logger, console_log_output, console_log_level, console_log_color, console_log_mask, log_line_template, date_fmt
|
|
138
|
-
):
|
|
138
|
+
def setup_console_output(logger, console_log_output, console_log_level, console_log_color, console_log_mask, log_line_template, date_fmt):
|
|
139
139
|
if not date_fmt:
|
|
140
140
|
date_fmt = coloredlogs.DEFAULT_DATE_FORMAT
|
|
141
141
|
coloredlogs.install(
|
hcs_core/ctxp/profile.py
CHANGED
|
@@ -20,7 +20,7 @@ from copy import deepcopy
|
|
|
20
20
|
from typing import Any
|
|
21
21
|
|
|
22
22
|
from . import state
|
|
23
|
-
from .data_util import
|
|
23
|
+
from .data_util import deep_set_attr, load_data_file, save_data_file
|
|
24
24
|
from .jsondot import dotdict, dotify
|
|
25
25
|
from .util import CtxpException, panic
|
|
26
26
|
|
|
@@ -63,7 +63,7 @@ def create(name: str, data: dict, auto_use: bool = True, overwrite: bool = False
|
|
|
63
63
|
save_data_file(data, file(name), "yaml", 0o600)
|
|
64
64
|
if auto_use:
|
|
65
65
|
use(name)
|
|
66
|
-
return get(name)
|
|
66
|
+
return get(name, reload=True)
|
|
67
67
|
|
|
68
68
|
|
|
69
69
|
def copy(from_name: str, to_name: str, overwrite: bool = True):
|
|
@@ -82,9 +82,7 @@ def current(reload: bool = False, exit_on_failure: bool = True, exclude_secret:
|
|
|
82
82
|
data = get(profile_name, reload)
|
|
83
83
|
|
|
84
84
|
if data is None and exit_on_failure:
|
|
85
|
-
panic(
|
|
86
|
-
"Profile not set. Use 'hcs profile use [profile-name]' to choose one, or use 'hcs profile init' to create default profiles."
|
|
87
|
-
)
|
|
85
|
+
panic("Profile not set. Use 'hcs profile use [profile-name]' to choose one, or use 'hcs profile init' to create default profiles.")
|
|
88
86
|
|
|
89
87
|
if exclude_secret:
|
|
90
88
|
data = dotdict(dict(data))
|
|
@@ -98,7 +96,6 @@ def current(reload: bool = False, exit_on_failure: bool = True, exclude_secret:
|
|
|
98
96
|
|
|
99
97
|
def save():
|
|
100
98
|
"""Save the current profile"""
|
|
101
|
-
global _data
|
|
102
99
|
if _data is None:
|
|
103
100
|
return
|
|
104
101
|
write(name(), _data)
|
|
@@ -127,6 +124,8 @@ def use(name: str) -> str:
|
|
|
127
124
|
return
|
|
128
125
|
|
|
129
126
|
_set_active_profile_name(name)
|
|
127
|
+
global _data
|
|
128
|
+
_data = _load_data_file_with_env_overrides(name, None)
|
|
130
129
|
return name
|
|
131
130
|
|
|
132
131
|
|
|
@@ -224,10 +223,10 @@ class auth:
|
|
|
224
223
|
if json.dumps(data) == json.dumps(_auth_cache):
|
|
225
224
|
return
|
|
226
225
|
_auth_cache = deepcopy(data)
|
|
227
|
-
file_name = auth._file_name()
|
|
228
226
|
if os.environ.get("CTXP_AUTH_PERSIST", "true").lower() == "false":
|
|
229
227
|
pass
|
|
230
228
|
else:
|
|
229
|
+
file_name = auth._file_name()
|
|
231
230
|
save_data_file(data, file_name, "yaml", 0o600)
|
|
232
231
|
|
|
233
232
|
@staticmethod
|
hcs_core/ctxp/recent.py
CHANGED
|
@@ -31,7 +31,7 @@ def all():
|
|
|
31
31
|
return _recent
|
|
32
32
|
|
|
33
33
|
|
|
34
|
-
def require(
|
|
34
|
+
def require(k: str, provided):
|
|
35
35
|
if not k:
|
|
36
36
|
raise Exception("Missing 'key' in recent.require(current, key).")
|
|
37
37
|
|
|
@@ -79,9 +79,9 @@ def of(k: str):
|
|
|
79
79
|
|
|
80
80
|
class helper:
|
|
81
81
|
@staticmethod
|
|
82
|
-
def default_list(array: list, name: str):
|
|
82
|
+
def default_list(array: list, name: str, field_name: str = "id"):
|
|
83
83
|
with of(name) as r:
|
|
84
84
|
if len(array) == 1:
|
|
85
|
-
r.set(array[0][
|
|
85
|
+
r.set(array[0][field_name])
|
|
86
86
|
else:
|
|
87
87
|
r.unset()
|
hcs_core/ctxp/state.py
CHANGED
|
@@ -31,7 +31,7 @@ class _StateFile:
|
|
|
31
31
|
self._cache = jsondot.load(self._path, {}, lock=True)
|
|
32
32
|
return self._cache
|
|
33
33
|
|
|
34
|
-
def get(self, key: str, default: Any, reload: bool = False):
|
|
34
|
+
def get(self, key: str, default: Any = None, reload: bool = False):
|
|
35
35
|
return self._data(reload).get(key, default)
|
|
36
36
|
|
|
37
37
|
def set(self, key: str, value: Any):
|
|
@@ -47,7 +47,7 @@ def init(store_path: str, name: str):
|
|
|
47
47
|
_file = _StateFile(os.path.join(store_path, name))
|
|
48
48
|
|
|
49
49
|
|
|
50
|
-
def get(key: str, default: Any, reload: bool = False):
|
|
50
|
+
def get(key: str, default: Any = None, reload: bool = False):
|
|
51
51
|
return _file.get(key=key, default=default, reload=reload)
|
|
52
52
|
|
|
53
53
|
|
hcs_core/ctxp/task_schd.py
CHANGED
|
@@ -111,7 +111,6 @@ def statistics(reset_cycle: bool = False):
|
|
|
111
111
|
|
|
112
112
|
def _daemon_worker():
|
|
113
113
|
log.info("task scheduler daemon thread start")
|
|
114
|
-
global _g_flag_stop_daemon
|
|
115
114
|
while not _g_flag_stop_daemon.is_set():
|
|
116
115
|
if _g_flag_running:
|
|
117
116
|
schedule.run_pending()
|
|
@@ -149,7 +148,6 @@ def resume():
|
|
|
149
148
|
|
|
150
149
|
|
|
151
150
|
def stop_daemon():
|
|
152
|
-
global _g_flag_stop_daemon
|
|
153
151
|
global _g_worker_thread
|
|
154
152
|
if _g_worker_thread:
|
|
155
153
|
_g_flag_stop_daemon.set()
|