jsrc 0.2.3__tar.gz → 0.2.4__tar.gz
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.
- {jsrc-0.2.3/src/jsrc.egg-info → jsrc-0.2.4}/PKG-INFO +1 -1
- {jsrc-0.2.3 → jsrc-0.2.4}/pyproject.toml +1 -1
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/analyze/__init__.py +1 -3
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/cli.py +1 -1
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/grn/__init__.py +3 -1
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/grn/anno2json.py +4 -4
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/grn/build.py +1 -1
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/grn/net2json.py +2 -2
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/grn/serve.py +1 -1
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/grn/sources/script.js +3 -3
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/gs/__init__.py +3 -1
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/job/__init__.py +3 -2
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/job/core.py +63 -15
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/job/gc.py +2 -2
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/job/history.py +5 -11
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/job/kill.py +2 -2
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/job/ls.py +9 -8
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/job/submit.py +2 -2
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/plot/__init__.py +4 -4
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/seq/__init__.py +3 -1
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/vision/__init__.py +1 -3
- {jsrc-0.2.3 → jsrc-0.2.4/src/jsrc.egg-info}/PKG-INFO +1 -1
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc.egg-info/SOURCES.txt +0 -1
- {jsrc-0.2.3 → jsrc-0.2.4}/tests/test_grn_conversion.py +10 -1
- jsrc-0.2.3/src/jsrc/job/show.py +0 -51
- {jsrc-0.2.3 → jsrc-0.2.4}/LICENSE +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/README.md +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/setup.cfg +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/__init__.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/analyze/bootstrap_phylo.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/analyze/core.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/analyze/motif.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/analyze/msa_consensus.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/analyze/phylo.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/analyze/qc.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/analyze/snpindel.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/core.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/grn/centrality.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/grn/core.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/grn/sources/index.html +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/grn/sources/style.css +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/gs/build.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/gs/split.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/gs/train.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/job/logs.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/plot/chromosome.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/plot/circoslite.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/plot/cis.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/plot/core.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/plot/domain.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/plot/dotplot.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/plot/exon.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/plot/gene.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/plot/heart.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/plot/rose.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/seq/codon.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/seq/core.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/seq/digest.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/seq/extract.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/seq/fetch.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/seq/kmer.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/seq/promoter.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/seq/qc.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/seq/rename.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/seq/translate.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/seq/window.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/vision/core.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/vision/efd.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/vision/extract.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/vision/traits.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc.egg-info/dependency_links.txt +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc.egg-info/entry_points.txt +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc.egg-info/requires.txt +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc.egg-info/top_level.txt +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/tests/test_analyze_extra.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/tests/test_analyze_phylo.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/tests/test_cli_error_behavior.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/tests/test_cli_module_flows.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/tests/test_gs_train.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/tests/test_job_core.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/tests/test_job_portability.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/tests/test_plot_commands.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/tests/test_plot_core.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/tests/test_progress.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/tests/test_seq_codon_kmer.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/tests/test_seq_core.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/tests/test_seq_digest.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/tests/test_seq_extract.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/tests/test_seq_fetch.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/tests/test_seq_promoter.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/tests/test_seq_qc.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/tests/test_seq_rename.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/tests/test_seq_translate.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/tests/test_seq_window.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/tests/test_vision_efd.py +0 -0
- {jsrc-0.2.3 → jsrc-0.2.4}/tests/test_vision_extract.py +0 -0
|
@@ -28,9 +28,7 @@ def _register_selected_subcommand(subparsers: Any, selected: str) -> bool:
|
|
|
28
28
|
return True
|
|
29
29
|
|
|
30
30
|
|
|
31
|
-
def register_subparser(
|
|
32
|
-
subparsers: Any, selected_subcommand: str | None = None
|
|
33
|
-
) -> None:
|
|
31
|
+
def register_subparser(subparsers: Any, selected_subcommand: str | None = None) -> None:
|
|
34
32
|
analyze_parser = subparsers.add_parser("analyze", help="Analysis tools")
|
|
35
33
|
analyze_sub = analyze_parser.add_subparsers(dest="analyze_cmd")
|
|
36
34
|
analyze_parser.set_defaults(_group_parser=analyze_parser)
|
|
@@ -105,7 +105,7 @@ def _probe_route(argv: list[str]) -> tuple[str | None, str | None]:
|
|
|
105
105
|
def main() -> None:
|
|
106
106
|
argv = sys.argv[1:]
|
|
107
107
|
debug_mode = "--debug" in argv
|
|
108
|
-
verbose = "--verbose" in
|
|
108
|
+
verbose = "--verbose" in argv or debug_mode
|
|
109
109
|
setup_logging(verbose=verbose)
|
|
110
110
|
enabled_modules = _iter_enabled_modules()
|
|
111
111
|
if not enabled_modules:
|
|
@@ -31,6 +31,8 @@ def register_subparser(subparsers: Any, selected_subcommand: str | None = None)
|
|
|
31
31
|
grn_parser = subparsers.add_parser("grn", help="GRN conversion and local viewer")
|
|
32
32
|
grn_sub = grn_parser.add_subparsers(dest="grn_cmd")
|
|
33
33
|
grn_parser.set_defaults(_group_parser=grn_parser)
|
|
34
|
-
if selected_subcommand and _register_selected_subcommand(
|
|
34
|
+
if selected_subcommand and _register_selected_subcommand(
|
|
35
|
+
grn_sub, selected_subcommand
|
|
36
|
+
):
|
|
35
37
|
return
|
|
36
38
|
_register_stub_subcommands(grn_sub)
|
|
@@ -15,10 +15,10 @@ def annotation_to_json(input_path: str, output_path: str) -> dict[str, dict[str,
|
|
|
15
15
|
for row in reader:
|
|
16
16
|
if not row:
|
|
17
17
|
continue
|
|
18
|
-
gid = str(row[0])
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
anno[gid] = {"p":
|
|
18
|
+
gid = str(row[0])
|
|
19
|
+
desc = str(row[1]) if len(row) > 1 else ""
|
|
20
|
+
map_id = str(row[2]) if len(row) > 2 else ""
|
|
21
|
+
anno[gid] = {"p": map_id, "d": desc}
|
|
22
22
|
write_json(output_path, anno)
|
|
23
23
|
logger.info("Annotation JSON written: %s", output_path)
|
|
24
24
|
return anno
|
|
@@ -57,7 +57,7 @@ def _zip_viewer(viewer_dir: Path, zip_output: str) -> None:
|
|
|
57
57
|
|
|
58
58
|
def cmd(args: Namespace) -> None:
|
|
59
59
|
root = Path(args.dir).expanduser().resolve()
|
|
60
|
-
view_mode = "expand" if args.expand else "auto"
|
|
60
|
+
view_mode = "full" if args.all else "expand" if args.expand else "auto"
|
|
61
61
|
_sync_assets(str(root), view_mode, args.threshold, args.max_nodes)
|
|
62
62
|
if args.grn_json:
|
|
63
63
|
shutil.copy(args.grn_json, root / "json" / "grn.json")
|
|
@@ -17,8 +17,8 @@ def network_to_json(
|
|
|
17
17
|
for row in reader:
|
|
18
18
|
if len(row) < 3:
|
|
19
19
|
continue
|
|
20
|
-
source_id = str(row[0])
|
|
21
|
-
target_id = str(row[1])
|
|
20
|
+
source_id = str(row[0])
|
|
21
|
+
target_id = str(row[1])
|
|
22
22
|
try:
|
|
23
23
|
weight = float(row[2])
|
|
24
24
|
except ValueError:
|
|
@@ -10,7 +10,7 @@ from jsrc.grn.build import _sync_assets
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
def cmd(args: Namespace) -> None:
|
|
13
|
-
view_mode = "expand" if args.expand else "auto"
|
|
13
|
+
view_mode = "full" if args.all else "expand" if args.expand else "auto"
|
|
14
14
|
_sync_assets(args.dir, view_mode, args.threshold, 0)
|
|
15
15
|
ensure_dir(f"{args.dir}/json")
|
|
16
16
|
src_grn = os.path.abspath(args.grn_json)
|
|
@@ -229,7 +229,7 @@ function highlightNode(id) {
|
|
|
229
229
|
function downloadListInfo() {
|
|
230
230
|
if (!currentCenterId) return;
|
|
231
231
|
|
|
232
|
-
let content = "GeneID\
|
|
232
|
+
let content = "GeneID\tMapID\tAnnotation\tRelation\tWeight\n";
|
|
233
233
|
|
|
234
234
|
const centerInfo = annotations[currentCenterId] || { p: "", d: "" };
|
|
235
235
|
content += `${currentCenterId}\t${centerInfo.p}\t${centerInfo.d.replace(/[\n\r]/g, " ")}\tCenter\t-\n`;
|
|
@@ -278,7 +278,7 @@ function updateInfoPanel() {
|
|
|
278
278
|
header.innerHTML = `
|
|
279
279
|
<div class="panel-title">${currentCenterId}</div>
|
|
280
280
|
<div class="panel-desc">
|
|
281
|
-
<b>
|
|
281
|
+
<b>MapID:</b> ${info.p || '-'}<br>
|
|
282
282
|
${info.d || '-'}
|
|
283
283
|
</div>
|
|
284
284
|
<div class="panel-stats">
|
|
@@ -308,7 +308,7 @@ function updateInfoPanel() {
|
|
|
308
308
|
<span class="item-val">w:${n.val}</span>
|
|
309
309
|
</div>
|
|
310
310
|
<div class="item-details" onclick="event.stopPropagation()">
|
|
311
|
-
<div class="detail-line"><span class="detail-label">
|
|
311
|
+
<div class="detail-line"><span class="detail-label">MapID:</span> ${nInfo.p || '-'}</div>
|
|
312
312
|
<div class="detail-line"><span class="detail-label">Desc:</span> ${nInfo.d || '-'}</div>
|
|
313
313
|
</div>
|
|
314
314
|
`;
|
|
@@ -31,6 +31,8 @@ def register_subparser(subparsers: Any, selected_subcommand: str | None = None)
|
|
|
31
31
|
)
|
|
32
32
|
gs_sub = gs_parser.add_subparsers(dest="gs_cmd")
|
|
33
33
|
gs_parser.set_defaults(_group_parser=gs_parser)
|
|
34
|
-
if selected_subcommand and _register_selected_subcommand(
|
|
34
|
+
if selected_subcommand and _register_selected_subcommand(
|
|
35
|
+
gs_sub, selected_subcommand
|
|
36
|
+
):
|
|
35
37
|
return
|
|
36
38
|
_register_stub_subcommands(gs_sub)
|
|
@@ -4,7 +4,6 @@ from typing import Any
|
|
|
4
4
|
_SUBCOMMANDS: dict[str, tuple[str, str]] = {
|
|
5
5
|
"submit": ("jsrc.job.submit", "Submit a background job"),
|
|
6
6
|
"ls": ("jsrc.job.ls", "List jobs"),
|
|
7
|
-
"show": ("jsrc.job.show", "Show one job"),
|
|
8
7
|
"logs": ("jsrc.job.logs", "Show job logs"),
|
|
9
8
|
"kill": ("jsrc.job.kill", "Terminate a running job"),
|
|
10
9
|
"history": ("jsrc.job.history", "Show job history"),
|
|
@@ -33,6 +32,8 @@ def register_subparser(subparsers: Any, selected_subcommand: str | None = None)
|
|
|
33
32
|
job_parser = subparsers.add_parser("job", help="Track and manage background jobs")
|
|
34
33
|
job_sub = job_parser.add_subparsers(dest="job_cmd")
|
|
35
34
|
job_parser.set_defaults(_group_parser=job_parser)
|
|
36
|
-
if selected_subcommand and _register_selected_subcommand(
|
|
35
|
+
if selected_subcommand and _register_selected_subcommand(
|
|
36
|
+
job_sub, selected_subcommand
|
|
37
|
+
):
|
|
37
38
|
return
|
|
38
39
|
_register_stub_subcommands(job_sub)
|
|
@@ -17,7 +17,6 @@ _PLATFORM_NOTE_EMITTED = False
|
|
|
17
17
|
|
|
18
18
|
FIELDS = [
|
|
19
19
|
"job_id",
|
|
20
|
-
"name",
|
|
21
20
|
"submit_time",
|
|
22
21
|
"start_time",
|
|
23
22
|
"end_time",
|
|
@@ -35,6 +34,8 @@ FIELDS = [
|
|
|
35
34
|
"command",
|
|
36
35
|
]
|
|
37
36
|
|
|
37
|
+
DEFAULT_KEEP = 100
|
|
38
|
+
|
|
38
39
|
|
|
39
40
|
def now_iso() -> str:
|
|
40
41
|
return datetime.now(timezone.utc).astimezone().isoformat(timespec="seconds")
|
|
@@ -54,6 +55,13 @@ def to_float(value: str, default: float = 0.0) -> float:
|
|
|
54
55
|
return default
|
|
55
56
|
|
|
56
57
|
|
|
58
|
+
def config_home() -> Path:
|
|
59
|
+
xdg = os.getenv("XDG_CONFIG_HOME")
|
|
60
|
+
if xdg:
|
|
61
|
+
return Path(xdg).expanduser() / "jsrc"
|
|
62
|
+
return Path.home() / ".config" / "jsrc"
|
|
63
|
+
|
|
64
|
+
|
|
57
65
|
def data_home() -> Path:
|
|
58
66
|
xdg = os.getenv("XDG_DATA_HOME")
|
|
59
67
|
if xdg:
|
|
@@ -65,7 +73,7 @@ def history_path() -> Path:
|
|
|
65
73
|
override = os.getenv("JSRC_JOBS_FILE", "")
|
|
66
74
|
if override:
|
|
67
75
|
return Path(override).expanduser()
|
|
68
|
-
return
|
|
76
|
+
return config_home() / "job" / "history"
|
|
69
77
|
|
|
70
78
|
|
|
71
79
|
def default_log_dir() -> Path:
|
|
@@ -82,20 +90,35 @@ def ensure_dirs() -> None:
|
|
|
82
90
|
state_dir().mkdir(parents=True, exist_ok=True)
|
|
83
91
|
|
|
84
92
|
|
|
93
|
+
def _migrate_old_history() -> None:
|
|
94
|
+
old = data_home() / "jobs"
|
|
95
|
+
new = history_path()
|
|
96
|
+
if old.exists() and not new.exists():
|
|
97
|
+
new.parent.mkdir(parents=True, exist_ok=True)
|
|
98
|
+
old.rename(new)
|
|
99
|
+
|
|
100
|
+
|
|
85
101
|
def load_jobs() -> list[dict[str, str]]:
|
|
102
|
+
_migrate_old_history()
|
|
86
103
|
path = history_path()
|
|
87
104
|
if not path.exists():
|
|
88
105
|
return []
|
|
106
|
+
rows = []
|
|
89
107
|
with path.open("r", encoding="utf-8", newline="") as fh:
|
|
90
108
|
reader = csv.DictReader(fh, delimiter="\t")
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
109
|
+
for i, row in enumerate(reader):
|
|
110
|
+
try:
|
|
111
|
+
rows.append({k: row.get(k, "") for k in FIELDS})
|
|
112
|
+
except Exception:
|
|
113
|
+
logger.warning("job history line %d: skipping malformed entry", i + 2)
|
|
114
|
+
continue
|
|
115
|
+
return rows
|
|
95
116
|
|
|
96
117
|
|
|
97
118
|
def write_jobs(rows: list[dict[str, str]], keep: int | None = None) -> None:
|
|
98
|
-
if keep is
|
|
119
|
+
if keep is None:
|
|
120
|
+
keep = DEFAULT_KEEP
|
|
121
|
+
if keep > 0 and len(rows) > keep:
|
|
99
122
|
rows = rows[-keep:]
|
|
100
123
|
path = history_path()
|
|
101
124
|
with path.open("w", encoding="utf-8", newline="") as fh:
|
|
@@ -275,7 +298,11 @@ def to_row_view(row: dict[str, str], live: dict[str, str]) -> dict[str, str]:
|
|
|
275
298
|
avg_kb = int(sum_kb / samples) if samples > 0 else rss_kb
|
|
276
299
|
runtime_sec = runtime_seconds(row, live)
|
|
277
300
|
out = dict(row)
|
|
278
|
-
|
|
301
|
+
rss_mb_val = rss_kb / 1024
|
|
302
|
+
rss_display = f"{rss_mb_val:.1f}" if rss_mb_val < 1024 else f"{rss_mb_val / 1024:.1f}g"
|
|
303
|
+
out["rss_mb"] = rss_display
|
|
304
|
+
out["rss"] = rss_display
|
|
305
|
+
out["mem"] = rss_display
|
|
279
306
|
out["rss_min_mb"] = f"{min_kb / 1024:.1f}"
|
|
280
307
|
out["rss_avg_mb"] = f"{avg_kb / 1024:.1f}"
|
|
281
308
|
out["rss_peak_mb"] = f"{peak_kb / 1024:.1f}"
|
|
@@ -285,6 +312,19 @@ def to_row_view(row: dict[str, str], live: dict[str, str]) -> dict[str, str]:
|
|
|
285
312
|
out["runtime"] = format_duration(runtime_sec)
|
|
286
313
|
out["cpu_pct"] = f"{to_float(live.get('pcpu', '0'), 0.0):.1f}"
|
|
287
314
|
out["state"] = live.get("stat", "")
|
|
315
|
+
st = row.get("status", "")
|
|
316
|
+
out["s"] = {"running": "R", "exited": "E", "failed": "F", "killed": "K", "lost": "L"}.get(
|
|
317
|
+
st, st
|
|
318
|
+
)
|
|
319
|
+
submit = row.get("submit_time", "")
|
|
320
|
+
if submit:
|
|
321
|
+
try:
|
|
322
|
+
dt = datetime.fromisoformat(submit)
|
|
323
|
+
out["time"] = f"{dt.strftime('%Y-%m-%d %H:%M')} / {out.get('runtime', '')}"
|
|
324
|
+
except (TypeError, ValueError):
|
|
325
|
+
out["time"] = f"{submit} / {out.get('runtime', '')}"
|
|
326
|
+
else:
|
|
327
|
+
out["time"] = f" / {out.get('runtime', '')}"
|
|
288
328
|
return out
|
|
289
329
|
|
|
290
330
|
|
|
@@ -360,18 +400,26 @@ def filter_rows(rows: list[dict[str, str]], query: str) -> list[dict[str, str]]:
|
|
|
360
400
|
def sort_rows(
|
|
361
401
|
rows: list[dict[str, str]], key: str, reverse: bool
|
|
362
402
|
) -> list[dict[str, str]]:
|
|
363
|
-
if key
|
|
403
|
+
if key in {"submit_time", "time"}:
|
|
364
404
|
return sorted(rows, key=lambda r: r.get("submit_time", ""), reverse=reverse)
|
|
365
405
|
if key == "pid":
|
|
366
406
|
return sorted(rows, key=lambda r: to_int(r.get("pid", "0")), reverse=reverse)
|
|
367
407
|
if key == "job_id":
|
|
368
408
|
return sorted(rows, key=lambda r: to_int(r.get("job_id", "0")), reverse=reverse)
|
|
369
|
-
if key
|
|
409
|
+
if key in {"status", "s"}:
|
|
370
410
|
return sorted(rows, key=lambda r: r.get("status", ""), reverse=reverse)
|
|
371
411
|
if key == "rss_mb":
|
|
372
412
|
return sorted(
|
|
373
413
|
rows, key=lambda r: to_float(r.get("rss_mb", "0"), 0.0), reverse=reverse
|
|
374
414
|
)
|
|
415
|
+
if key == "rss":
|
|
416
|
+
return sorted(
|
|
417
|
+
rows, key=lambda r: to_int(r.get("rss_kb_last", "0"), 0), reverse=reverse
|
|
418
|
+
)
|
|
419
|
+
if key == "mem":
|
|
420
|
+
return sorted(
|
|
421
|
+
rows, key=lambda r: to_int(r.get("rss_kb_last", "0"), 0), reverse=reverse
|
|
422
|
+
)
|
|
375
423
|
if key == "rss_min_mb":
|
|
376
424
|
return sorted(
|
|
377
425
|
rows, key=lambda r: to_float(r.get("rss_min_mb", "0"), 0.0), reverse=reverse
|
|
@@ -397,15 +445,15 @@ def print_table(rows: list[dict[str, str]], columns: list[str]) -> None:
|
|
|
397
445
|
if not rows:
|
|
398
446
|
print("(no records)")
|
|
399
447
|
return
|
|
400
|
-
widths = {c: len(c) for c in columns}
|
|
448
|
+
widths = {c: len(c.upper()) for c in columns}
|
|
401
449
|
for row in rows:
|
|
402
450
|
for c in columns:
|
|
403
451
|
widths[c] = max(widths[c], len(str(row.get(c, ""))))
|
|
404
|
-
header = "
|
|
452
|
+
header = " ".join(c.upper().ljust(widths[c]) for c in columns)
|
|
405
453
|
print(header)
|
|
406
|
-
print("
|
|
454
|
+
print(" ".join("-" * widths[c] for c in columns))
|
|
407
455
|
for row in rows:
|
|
408
|
-
print("
|
|
456
|
+
print(" ".join(str(row.get(c, "")).ljust(widths[c]) for c in columns))
|
|
409
457
|
|
|
410
458
|
|
|
411
459
|
def print_rows(rows: list[dict[str, str]], columns: list[str], fmt: str) -> None:
|
|
@@ -466,7 +514,7 @@ def collect_render_rows(args: Any, refresh: bool) -> list[dict[str, str]]:
|
|
|
466
514
|
if refresh:
|
|
467
515
|
rows, changed = refresh_jobs(rows)
|
|
468
516
|
if changed:
|
|
469
|
-
write_jobs(rows
|
|
517
|
+
write_jobs(rows)
|
|
470
518
|
rows = filter_rows(rows, args.query)
|
|
471
519
|
rendered = []
|
|
472
520
|
for row in rows:
|
|
@@ -3,7 +3,7 @@ from argparse import Namespace
|
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
from typing import Any
|
|
5
5
|
|
|
6
|
-
from jsrc.job.core import load_jobs, now_iso, runtime_seconds, state_dir, write_jobs
|
|
6
|
+
from jsrc.job.core import DEFAULT_KEEP, load_jobs, now_iso, runtime_seconds, state_dir, write_jobs
|
|
7
7
|
|
|
8
8
|
logger = logging.getLogger(__name__)
|
|
9
9
|
|
|
@@ -40,7 +40,7 @@ def register(subparsers: Any) -> None:
|
|
|
40
40
|
"-k",
|
|
41
41
|
"--keep-history",
|
|
42
42
|
type=int,
|
|
43
|
-
default=
|
|
43
|
+
default=DEFAULT_KEEP,
|
|
44
44
|
help="Keep last N history records",
|
|
45
45
|
)
|
|
46
46
|
p.add_argument(
|
|
@@ -5,6 +5,7 @@ from jsrc.job.core import (
|
|
|
5
5
|
filter_rows,
|
|
6
6
|
load_jobs,
|
|
7
7
|
print_rows,
|
|
8
|
+
refresh_jobs,
|
|
8
9
|
to_row_view,
|
|
9
10
|
warn_portability_limits,
|
|
10
11
|
)
|
|
@@ -13,6 +14,7 @@ from jsrc.job.core import (
|
|
|
13
14
|
def cmd(args: Namespace) -> None:
|
|
14
15
|
warn_portability_limits()
|
|
15
16
|
rows = load_jobs()
|
|
17
|
+
rows, _ = refresh_jobs(rows)
|
|
16
18
|
rows = filter_rows(rows, args.query)
|
|
17
19
|
if args.limit > 0:
|
|
18
20
|
rows = rows[-args.limit :]
|
|
@@ -21,18 +23,10 @@ def cmd(args: Namespace) -> None:
|
|
|
21
23
|
view = to_row_view(row, {})
|
|
22
24
|
rendered.append(view)
|
|
23
25
|
cols = [
|
|
24
|
-
"job_id",
|
|
25
|
-
"status",
|
|
26
26
|
"pid",
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"runtime_sec",
|
|
31
|
-
"rss_mb",
|
|
32
|
-
"rss_min_mb",
|
|
33
|
-
"rss_avg_mb",
|
|
34
|
-
"rss_peak_mb",
|
|
35
|
-
"log_path",
|
|
27
|
+
"s",
|
|
28
|
+
"mem",
|
|
29
|
+
"time",
|
|
36
30
|
"command",
|
|
37
31
|
]
|
|
38
32
|
print_rows(rendered, cols, args.format)
|
|
@@ -30,12 +30,12 @@ def cmd(args: Namespace) -> None:
|
|
|
30
30
|
os.killpg(pgid, sig)
|
|
31
31
|
else:
|
|
32
32
|
os.kill(pid, sig)
|
|
33
|
-
except ProcessLookupError:
|
|
33
|
+
except (ProcessLookupError, FileNotFoundError):
|
|
34
34
|
pass
|
|
35
35
|
row["status"] = "killed"
|
|
36
36
|
row["end_time"] = now_iso()
|
|
37
37
|
row["runtime_sec"] = str(runtime_seconds(row, {}))
|
|
38
|
-
write_jobs(rows
|
|
38
|
+
write_jobs(rows)
|
|
39
39
|
print(f"killed\t{pid}")
|
|
40
40
|
print(f"signal\t{args.signal}")
|
|
41
41
|
|
|
@@ -16,14 +16,10 @@ def cmd(args: Namespace) -> None:
|
|
|
16
16
|
columns = [c.strip() for c in args.cols.split(",") if c.strip()]
|
|
17
17
|
if not columns:
|
|
18
18
|
columns = [
|
|
19
|
-
"job_id",
|
|
20
|
-
"status",
|
|
21
19
|
"pid",
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"rss_avg_mb",
|
|
26
|
-
"rss_peak_mb",
|
|
20
|
+
"s",
|
|
21
|
+
"mem",
|
|
22
|
+
"time",
|
|
27
23
|
"command",
|
|
28
24
|
]
|
|
29
25
|
|
|
@@ -39,6 +35,7 @@ def cmd(args: Namespace) -> None:
|
|
|
39
35
|
sys.stdout.flush()
|
|
40
36
|
time.sleep(max(args.interval, 0.2))
|
|
41
37
|
except KeyboardInterrupt:
|
|
38
|
+
sys.stdout.write("\033[?25h")
|
|
42
39
|
return
|
|
43
40
|
rows = collect_render_rows(args, refresh=True)
|
|
44
41
|
print_rows(rows, columns, args.format)
|
|
@@ -53,7 +50,7 @@ def register(subparsers: Any) -> None:
|
|
|
53
50
|
p.add_argument(
|
|
54
51
|
"-c",
|
|
55
52
|
"--cols",
|
|
56
|
-
default="
|
|
53
|
+
default="pid,s,mem,time,command",
|
|
57
54
|
help="Columns to print, comma-separated",
|
|
58
55
|
)
|
|
59
56
|
p.add_argument(
|
|
@@ -68,16 +65,20 @@ def register(subparsers: Any) -> None:
|
|
|
68
65
|
"--sort",
|
|
69
66
|
choices=[
|
|
70
67
|
"submit_time",
|
|
68
|
+
"time",
|
|
71
69
|
"elapsed",
|
|
72
70
|
"runtime",
|
|
73
71
|
"runtime_sec",
|
|
74
72
|
"rss_mb",
|
|
73
|
+
"rss",
|
|
75
74
|
"rss_min_mb",
|
|
76
75
|
"rss_avg_mb",
|
|
77
76
|
"rss_peak_mb",
|
|
78
77
|
"pid",
|
|
79
78
|
"job_id",
|
|
80
79
|
"status",
|
|
80
|
+
"s",
|
|
81
|
+
"mem",
|
|
81
82
|
],
|
|
82
83
|
default="submit_time",
|
|
83
84
|
help="Sort field",
|
|
@@ -57,7 +57,6 @@ def cmd(args: Namespace) -> None:
|
|
|
57
57
|
now = now_iso()
|
|
58
58
|
row = {
|
|
59
59
|
"job_id": job_id,
|
|
60
|
-
"name": args.name,
|
|
61
60
|
"submit_time": now,
|
|
62
61
|
"start_time": now,
|
|
63
62
|
"end_time": "",
|
|
@@ -73,9 +72,10 @@ def cmd(args: Namespace) -> None:
|
|
|
73
72
|
"rss_samples": "1",
|
|
74
73
|
"runtime_sec": "0",
|
|
75
74
|
"command": args.command,
|
|
75
|
+
"name": args.name,
|
|
76
76
|
}
|
|
77
77
|
rows.append(row)
|
|
78
|
-
write_jobs(rows
|
|
78
|
+
write_jobs(rows)
|
|
79
79
|
print(f"job_id\t{job_id}")
|
|
80
80
|
print(f"pid\t{proc.pid}")
|
|
81
81
|
print(f"log\t{log_path}")
|
|
@@ -31,12 +31,12 @@ def _register_selected_subcommand(subparsers: Any, selected: str) -> bool:
|
|
|
31
31
|
return True
|
|
32
32
|
|
|
33
33
|
|
|
34
|
-
def register_subparser(
|
|
35
|
-
subparsers: Any, selected_subcommand: str | None = None
|
|
36
|
-
) -> None:
|
|
34
|
+
def register_subparser(subparsers: Any, selected_subcommand: str | None = None) -> None:
|
|
37
35
|
plot_parser = subparsers.add_parser("plot", help="Visualization")
|
|
38
36
|
plot_sub = plot_parser.add_subparsers(dest="plot_cmd")
|
|
39
37
|
plot_parser.set_defaults(_group_parser=plot_parser)
|
|
40
|
-
if selected_subcommand and _register_selected_subcommand(
|
|
38
|
+
if selected_subcommand and _register_selected_subcommand(
|
|
39
|
+
plot_sub, selected_subcommand
|
|
40
|
+
):
|
|
41
41
|
return
|
|
42
42
|
_register_stub_subcommands(plot_sub)
|
|
@@ -36,6 +36,8 @@ def register_subparser(subparsers: Any, selected_subcommand: str | None = None)
|
|
|
36
36
|
seq_parser = subparsers.add_parser("seq", help="Sequence operations")
|
|
37
37
|
seq_sub = seq_parser.add_subparsers(dest="seq_cmd")
|
|
38
38
|
seq_parser.set_defaults(_group_parser=seq_parser)
|
|
39
|
-
if selected_subcommand and _register_selected_subcommand(
|
|
39
|
+
if selected_subcommand and _register_selected_subcommand(
|
|
40
|
+
seq_sub, selected_subcommand
|
|
41
|
+
):
|
|
40
42
|
return
|
|
41
43
|
_register_stub_subcommands(seq_sub)
|
|
@@ -25,9 +25,7 @@ def _register_selected_subcommand(subparsers: Any, selected: str) -> bool:
|
|
|
25
25
|
return True
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
def register_subparser(
|
|
29
|
-
subparsers: Any, selected_subcommand: str | None = None
|
|
30
|
-
) -> None:
|
|
28
|
+
def register_subparser(subparsers: Any, selected_subcommand: str | None = None) -> None:
|
|
31
29
|
vision_parser = subparsers.add_parser(
|
|
32
30
|
"vision", help="Image recognition and shape descriptors"
|
|
33
31
|
)
|
|
@@ -62,7 +62,7 @@ class TestAnnotationToJson:
|
|
|
62
62
|
def test_basic_annotation(self, tmp_path):
|
|
63
63
|
tsv = tmp_path / "anno.tsv"
|
|
64
64
|
tsv.write_text(
|
|
65
|
-
"AT5G01010\
|
|
65
|
+
"AT5G01010\tAnthranilate synthase\tAT5G01010\n", encoding="utf-8"
|
|
66
66
|
)
|
|
67
67
|
out = tmp_path / "annotation.json"
|
|
68
68
|
|
|
@@ -101,3 +101,12 @@ class TestAnnotationToJson:
|
|
|
101
101
|
assert len(anno) == 2
|
|
102
102
|
assert "A" in anno
|
|
103
103
|
assert "D" in anno
|
|
104
|
+
|
|
105
|
+
def test_third_column_used_as_mapping_id(self, tmp_path):
|
|
106
|
+
tsv = tmp_path / "anno.tsv"
|
|
107
|
+
tsv.write_text("G1\tDesc\tAT1G01010\n", encoding="utf-8")
|
|
108
|
+
out = tmp_path / "annotation.json"
|
|
109
|
+
|
|
110
|
+
anno = annotation_to_json(str(tsv), str(out))
|
|
111
|
+
assert anno["G1"]["p"] == "AT1G01010"
|
|
112
|
+
assert anno["G1"]["d"] == "Desc"
|
jsrc-0.2.3/src/jsrc/job/show.py
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
from argparse import Namespace
|
|
2
|
-
from typing import Any
|
|
3
|
-
|
|
4
|
-
from jsrc.job.core import (
|
|
5
|
-
build_live,
|
|
6
|
-
find_row,
|
|
7
|
-
load_jobs,
|
|
8
|
-
refresh_jobs,
|
|
9
|
-
to_int,
|
|
10
|
-
to_row_view,
|
|
11
|
-
warn_portability_limits,
|
|
12
|
-
write_jobs,
|
|
13
|
-
print_rows,
|
|
14
|
-
)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def cmd(args: Namespace) -> None:
|
|
18
|
-
warn_portability_limits()
|
|
19
|
-
rows = load_jobs()
|
|
20
|
-
rows, changed = refresh_jobs(rows)
|
|
21
|
-
if changed:
|
|
22
|
-
write_jobs(rows, keep=1000)
|
|
23
|
-
row = find_row(rows, str(args.target))
|
|
24
|
-
if row is None:
|
|
25
|
-
raise SystemExit(f"job not found: {args.target}")
|
|
26
|
-
live = (
|
|
27
|
-
build_live(to_int(row.get("pid", "0"), 0))
|
|
28
|
-
if row.get("status", "") == "running"
|
|
29
|
-
else {}
|
|
30
|
-
)
|
|
31
|
-
view = to_row_view(row, live)
|
|
32
|
-
columns = (
|
|
33
|
-
[c.strip() for c in args.cols.split(",") if c.strip()]
|
|
34
|
-
if args.cols
|
|
35
|
-
else list(view.keys())
|
|
36
|
-
)
|
|
37
|
-
print_rows([view], columns, args.format)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def register(subparsers: Any) -> None:
|
|
41
|
-
p = subparsers.add_parser("show", help="Show details of a job by job_id or pid")
|
|
42
|
-
p.add_argument("target", help="Job ID or PID")
|
|
43
|
-
p.add_argument(
|
|
44
|
-
"-f",
|
|
45
|
-
"--format",
|
|
46
|
-
choices=["table", "json"],
|
|
47
|
-
default="table",
|
|
48
|
-
help="Output format",
|
|
49
|
-
)
|
|
50
|
-
p.add_argument("-c", "--cols", default="", help="Columns to print, comma-separated")
|
|
51
|
-
p.set_defaults(func=cmd)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|