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.
Files changed (96) hide show
  1. {jsrc-0.2.3/src/jsrc.egg-info → jsrc-0.2.4}/PKG-INFO +1 -1
  2. {jsrc-0.2.3 → jsrc-0.2.4}/pyproject.toml +1 -1
  3. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/analyze/__init__.py +1 -3
  4. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/cli.py +1 -1
  5. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/grn/__init__.py +3 -1
  6. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/grn/anno2json.py +4 -4
  7. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/grn/build.py +1 -1
  8. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/grn/net2json.py +2 -2
  9. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/grn/serve.py +1 -1
  10. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/grn/sources/script.js +3 -3
  11. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/gs/__init__.py +3 -1
  12. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/job/__init__.py +3 -2
  13. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/job/core.py +63 -15
  14. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/job/gc.py +2 -2
  15. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/job/history.py +5 -11
  16. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/job/kill.py +2 -2
  17. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/job/ls.py +9 -8
  18. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/job/submit.py +2 -2
  19. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/plot/__init__.py +4 -4
  20. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/seq/__init__.py +3 -1
  21. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/vision/__init__.py +1 -3
  22. {jsrc-0.2.3 → jsrc-0.2.4/src/jsrc.egg-info}/PKG-INFO +1 -1
  23. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc.egg-info/SOURCES.txt +0 -1
  24. {jsrc-0.2.3 → jsrc-0.2.4}/tests/test_grn_conversion.py +10 -1
  25. jsrc-0.2.3/src/jsrc/job/show.py +0 -51
  26. {jsrc-0.2.3 → jsrc-0.2.4}/LICENSE +0 -0
  27. {jsrc-0.2.3 → jsrc-0.2.4}/README.md +0 -0
  28. {jsrc-0.2.3 → jsrc-0.2.4}/setup.cfg +0 -0
  29. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/__init__.py +0 -0
  30. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/analyze/bootstrap_phylo.py +0 -0
  31. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/analyze/core.py +0 -0
  32. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/analyze/motif.py +0 -0
  33. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/analyze/msa_consensus.py +0 -0
  34. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/analyze/phylo.py +0 -0
  35. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/analyze/qc.py +0 -0
  36. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/analyze/snpindel.py +0 -0
  37. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/core.py +0 -0
  38. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/grn/centrality.py +0 -0
  39. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/grn/core.py +0 -0
  40. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/grn/sources/index.html +0 -0
  41. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/grn/sources/style.css +0 -0
  42. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/gs/build.py +0 -0
  43. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/gs/split.py +0 -0
  44. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/gs/train.py +0 -0
  45. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/job/logs.py +0 -0
  46. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/plot/chromosome.py +0 -0
  47. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/plot/circoslite.py +0 -0
  48. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/plot/cis.py +0 -0
  49. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/plot/core.py +0 -0
  50. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/plot/domain.py +0 -0
  51. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/plot/dotplot.py +0 -0
  52. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/plot/exon.py +0 -0
  53. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/plot/gene.py +0 -0
  54. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/plot/heart.py +0 -0
  55. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/plot/rose.py +0 -0
  56. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/seq/codon.py +0 -0
  57. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/seq/core.py +0 -0
  58. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/seq/digest.py +0 -0
  59. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/seq/extract.py +0 -0
  60. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/seq/fetch.py +0 -0
  61. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/seq/kmer.py +0 -0
  62. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/seq/promoter.py +0 -0
  63. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/seq/qc.py +0 -0
  64. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/seq/rename.py +0 -0
  65. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/seq/translate.py +0 -0
  66. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/seq/window.py +0 -0
  67. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/vision/core.py +0 -0
  68. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/vision/efd.py +0 -0
  69. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/vision/extract.py +0 -0
  70. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc/vision/traits.py +0 -0
  71. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc.egg-info/dependency_links.txt +0 -0
  72. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc.egg-info/entry_points.txt +0 -0
  73. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc.egg-info/requires.txt +0 -0
  74. {jsrc-0.2.3 → jsrc-0.2.4}/src/jsrc.egg-info/top_level.txt +0 -0
  75. {jsrc-0.2.3 → jsrc-0.2.4}/tests/test_analyze_extra.py +0 -0
  76. {jsrc-0.2.3 → jsrc-0.2.4}/tests/test_analyze_phylo.py +0 -0
  77. {jsrc-0.2.3 → jsrc-0.2.4}/tests/test_cli_error_behavior.py +0 -0
  78. {jsrc-0.2.3 → jsrc-0.2.4}/tests/test_cli_module_flows.py +0 -0
  79. {jsrc-0.2.3 → jsrc-0.2.4}/tests/test_gs_train.py +0 -0
  80. {jsrc-0.2.3 → jsrc-0.2.4}/tests/test_job_core.py +0 -0
  81. {jsrc-0.2.3 → jsrc-0.2.4}/tests/test_job_portability.py +0 -0
  82. {jsrc-0.2.3 → jsrc-0.2.4}/tests/test_plot_commands.py +0 -0
  83. {jsrc-0.2.3 → jsrc-0.2.4}/tests/test_plot_core.py +0 -0
  84. {jsrc-0.2.3 → jsrc-0.2.4}/tests/test_progress.py +0 -0
  85. {jsrc-0.2.3 → jsrc-0.2.4}/tests/test_seq_codon_kmer.py +0 -0
  86. {jsrc-0.2.3 → jsrc-0.2.4}/tests/test_seq_core.py +0 -0
  87. {jsrc-0.2.3 → jsrc-0.2.4}/tests/test_seq_digest.py +0 -0
  88. {jsrc-0.2.3 → jsrc-0.2.4}/tests/test_seq_extract.py +0 -0
  89. {jsrc-0.2.3 → jsrc-0.2.4}/tests/test_seq_fetch.py +0 -0
  90. {jsrc-0.2.3 → jsrc-0.2.4}/tests/test_seq_promoter.py +0 -0
  91. {jsrc-0.2.3 → jsrc-0.2.4}/tests/test_seq_qc.py +0 -0
  92. {jsrc-0.2.3 → jsrc-0.2.4}/tests/test_seq_rename.py +0 -0
  93. {jsrc-0.2.3 → jsrc-0.2.4}/tests/test_seq_translate.py +0 -0
  94. {jsrc-0.2.3 → jsrc-0.2.4}/tests/test_seq_window.py +0 -0
  95. {jsrc-0.2.3 → jsrc-0.2.4}/tests/test_vision_efd.py +0 -0
  96. {jsrc-0.2.3 → jsrc-0.2.4}/tests/test_vision_extract.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jsrc
3
- Version: 0.2.3
3
+ Version: 0.2.4
4
4
  Summary: Python library for bioinformatics and scientific computing
5
5
  Author-email: Jiaoyuan <imjiaoyuan@gmail.com>
6
6
  License-Expression: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "jsrc"
3
- version = "0.2.3"
3
+ version = "0.2.4"
4
4
  description = "Python library for bioinformatics and scientific computing"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -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 sys.argv[1:] or debug_mode
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(grn_sub, 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]).replace("_", "-")
19
- ptr = str(row[1]) if len(row) > 1 else ""
20
- desc = str(row[2]) if len(row) > 2 else ""
21
- anno[gid] = {"p": ptr, "d": desc}
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]).replace("_", "-")
21
- target_id = str(row[1]).replace("_", "-")
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\tPotriID\tAnnotation\tRelation\tWeight\n";
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>Ptr:</b> ${info.p || '-'}<br>
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">Potri:</span> ${nInfo.p || '-'}</div>
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(gs_sub, 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(job_sub, 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 data_home() / "jobs"
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
- rows = []
92
- for row in reader:
93
- rows.append({k: row.get(k, "") for k in FIELDS})
94
- return rows
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 not None and keep > 0 and len(rows) > keep:
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
- out["rss_mb"] = f"{rss_kb / 1024:.1f}"
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 == "submit_time":
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 == "status":
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 = " ".join(c.ljust(widths[c]) for c in columns)
452
+ header = " ".join(c.upper().ljust(widths[c]) for c in columns)
405
453
  print(header)
406
- print(" ".join("-" * widths[c] for c in columns))
454
+ print(" ".join("-" * widths[c] for c in columns))
407
455
  for row in rows:
408
- print(" ".join(str(row.get(c, "")).ljust(widths[c]) for c in columns))
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, keep=1000)
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=1000,
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
- "submit_time",
28
- "end_time",
29
- "runtime",
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, keep=1000)
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
- "runtime",
23
- "rss_mb",
24
- "rss_min_mb",
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="job_id,status,pid,runtime,rss_mb,rss_min_mb,rss_avg_mb,rss_peak_mb,submit_time,command",
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, keep=1000)
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(plot_sub, 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(seq_sub, 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
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jsrc
3
- Version: 0.2.3
3
+ Version: 0.2.4
4
4
  Summary: Python library for bioinformatics and scientific computing
5
5
  Author-email: Jiaoyuan <imjiaoyuan@gmail.com>
6
6
  License-Expression: MIT
@@ -39,7 +39,6 @@ src/jsrc/job/history.py
39
39
  src/jsrc/job/kill.py
40
40
  src/jsrc/job/logs.py
41
41
  src/jsrc/job/ls.py
42
- src/jsrc/job/show.py
43
42
  src/jsrc/job/submit.py
44
43
  src/jsrc/plot/__init__.py
45
44
  src/jsrc/plot/chromosome.py
@@ -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\tAT5G01010\tAnthranilate synthase\n", encoding="utf-8"
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"
@@ -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