jsrc 0.2.2__tar.gz → 0.2.3__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 (104) hide show
  1. {jsrc-0.2.2/src/jsrc.egg-info → jsrc-0.2.3}/PKG-INFO +1 -1
  2. {jsrc-0.2.2 → jsrc-0.2.3}/pyproject.toml +1 -1
  3. jsrc-0.2.3/src/jsrc/analyze/__init__.py +41 -0
  4. {jsrc-0.2.2 → jsrc-0.2.3}/src/jsrc/cli.py +78 -32
  5. jsrc-0.2.3/src/jsrc/grn/__init__.py +36 -0
  6. jsrc-0.2.3/src/jsrc/gs/__init__.py +36 -0
  7. jsrc-0.2.3/src/jsrc/job/__init__.py +38 -0
  8. jsrc-0.2.3/src/jsrc/plot/__init__.py +42 -0
  9. jsrc-0.2.3/src/jsrc/plot/heart.py +82 -0
  10. jsrc-0.2.3/src/jsrc/seq/__init__.py +41 -0
  11. jsrc-0.2.3/src/jsrc/vision/__init__.py +40 -0
  12. {jsrc-0.2.2 → jsrc-0.2.3/src/jsrc.egg-info}/PKG-INFO +1 -1
  13. {jsrc-0.2.2 → jsrc-0.2.3}/tests/test_cli_module_flows.py +56 -0
  14. jsrc-0.2.2/src/jsrc/analyze/__init__.py +0 -16
  15. jsrc-0.2.2/src/jsrc/grn/__init__.py +0 -15
  16. jsrc-0.2.2/src/jsrc/gs/__init__.py +0 -15
  17. jsrc-0.2.2/src/jsrc/job/__init__.py +0 -17
  18. jsrc-0.2.2/src/jsrc/plot/__init__.py +0 -29
  19. jsrc-0.2.2/src/jsrc/plot/heart.py +0 -37
  20. jsrc-0.2.2/src/jsrc/seq/__init__.py +0 -31
  21. jsrc-0.2.2/src/jsrc/vision/__init__.py +0 -15
  22. {jsrc-0.2.2 → jsrc-0.2.3}/LICENSE +0 -0
  23. {jsrc-0.2.2 → jsrc-0.2.3}/README.md +0 -0
  24. {jsrc-0.2.2 → jsrc-0.2.3}/setup.cfg +0 -0
  25. {jsrc-0.2.2 → jsrc-0.2.3}/src/jsrc/__init__.py +0 -0
  26. {jsrc-0.2.2 → jsrc-0.2.3}/src/jsrc/analyze/bootstrap_phylo.py +0 -0
  27. {jsrc-0.2.2 → jsrc-0.2.3}/src/jsrc/analyze/core.py +0 -0
  28. {jsrc-0.2.2 → jsrc-0.2.3}/src/jsrc/analyze/motif.py +0 -0
  29. {jsrc-0.2.2 → jsrc-0.2.3}/src/jsrc/analyze/msa_consensus.py +0 -0
  30. {jsrc-0.2.2 → jsrc-0.2.3}/src/jsrc/analyze/phylo.py +0 -0
  31. {jsrc-0.2.2 → jsrc-0.2.3}/src/jsrc/analyze/qc.py +0 -0
  32. {jsrc-0.2.2 → jsrc-0.2.3}/src/jsrc/analyze/snpindel.py +0 -0
  33. {jsrc-0.2.2 → jsrc-0.2.3}/src/jsrc/core.py +0 -0
  34. {jsrc-0.2.2 → jsrc-0.2.3}/src/jsrc/grn/anno2json.py +0 -0
  35. {jsrc-0.2.2 → jsrc-0.2.3}/src/jsrc/grn/build.py +0 -0
  36. {jsrc-0.2.2 → jsrc-0.2.3}/src/jsrc/grn/centrality.py +0 -0
  37. {jsrc-0.2.2 → jsrc-0.2.3}/src/jsrc/grn/core.py +0 -0
  38. {jsrc-0.2.2 → jsrc-0.2.3}/src/jsrc/grn/net2json.py +0 -0
  39. {jsrc-0.2.2 → jsrc-0.2.3}/src/jsrc/grn/serve.py +0 -0
  40. {jsrc-0.2.2 → jsrc-0.2.3}/src/jsrc/grn/sources/index.html +0 -0
  41. {jsrc-0.2.2 → jsrc-0.2.3}/src/jsrc/grn/sources/script.js +0 -0
  42. {jsrc-0.2.2 → jsrc-0.2.3}/src/jsrc/grn/sources/style.css +0 -0
  43. {jsrc-0.2.2 → jsrc-0.2.3}/src/jsrc/gs/build.py +0 -0
  44. {jsrc-0.2.2 → jsrc-0.2.3}/src/jsrc/gs/split.py +0 -0
  45. {jsrc-0.2.2 → jsrc-0.2.3}/src/jsrc/gs/train.py +0 -0
  46. {jsrc-0.2.2 → jsrc-0.2.3}/src/jsrc/job/core.py +0 -0
  47. {jsrc-0.2.2 → jsrc-0.2.3}/src/jsrc/job/gc.py +0 -0
  48. {jsrc-0.2.2 → jsrc-0.2.3}/src/jsrc/job/history.py +0 -0
  49. {jsrc-0.2.2 → jsrc-0.2.3}/src/jsrc/job/kill.py +0 -0
  50. {jsrc-0.2.2 → jsrc-0.2.3}/src/jsrc/job/logs.py +0 -0
  51. {jsrc-0.2.2 → jsrc-0.2.3}/src/jsrc/job/ls.py +0 -0
  52. {jsrc-0.2.2 → jsrc-0.2.3}/src/jsrc/job/show.py +0 -0
  53. {jsrc-0.2.2 → jsrc-0.2.3}/src/jsrc/job/submit.py +0 -0
  54. {jsrc-0.2.2 → jsrc-0.2.3}/src/jsrc/plot/chromosome.py +0 -0
  55. {jsrc-0.2.2 → jsrc-0.2.3}/src/jsrc/plot/circoslite.py +0 -0
  56. {jsrc-0.2.2 → jsrc-0.2.3}/src/jsrc/plot/cis.py +0 -0
  57. {jsrc-0.2.2 → jsrc-0.2.3}/src/jsrc/plot/core.py +0 -0
  58. {jsrc-0.2.2 → jsrc-0.2.3}/src/jsrc/plot/domain.py +0 -0
  59. {jsrc-0.2.2 → jsrc-0.2.3}/src/jsrc/plot/dotplot.py +0 -0
  60. {jsrc-0.2.2 → jsrc-0.2.3}/src/jsrc/plot/exon.py +0 -0
  61. {jsrc-0.2.2 → jsrc-0.2.3}/src/jsrc/plot/gene.py +0 -0
  62. {jsrc-0.2.2 → jsrc-0.2.3}/src/jsrc/plot/rose.py +0 -0
  63. {jsrc-0.2.2 → jsrc-0.2.3}/src/jsrc/seq/codon.py +0 -0
  64. {jsrc-0.2.2 → jsrc-0.2.3}/src/jsrc/seq/core.py +0 -0
  65. {jsrc-0.2.2 → jsrc-0.2.3}/src/jsrc/seq/digest.py +0 -0
  66. {jsrc-0.2.2 → jsrc-0.2.3}/src/jsrc/seq/extract.py +0 -0
  67. {jsrc-0.2.2 → jsrc-0.2.3}/src/jsrc/seq/fetch.py +0 -0
  68. {jsrc-0.2.2 → jsrc-0.2.3}/src/jsrc/seq/kmer.py +0 -0
  69. {jsrc-0.2.2 → jsrc-0.2.3}/src/jsrc/seq/promoter.py +0 -0
  70. {jsrc-0.2.2 → jsrc-0.2.3}/src/jsrc/seq/qc.py +0 -0
  71. {jsrc-0.2.2 → jsrc-0.2.3}/src/jsrc/seq/rename.py +0 -0
  72. {jsrc-0.2.2 → jsrc-0.2.3}/src/jsrc/seq/translate.py +0 -0
  73. {jsrc-0.2.2 → jsrc-0.2.3}/src/jsrc/seq/window.py +0 -0
  74. {jsrc-0.2.2 → jsrc-0.2.3}/src/jsrc/vision/core.py +0 -0
  75. {jsrc-0.2.2 → jsrc-0.2.3}/src/jsrc/vision/efd.py +0 -0
  76. {jsrc-0.2.2 → jsrc-0.2.3}/src/jsrc/vision/extract.py +0 -0
  77. {jsrc-0.2.2 → jsrc-0.2.3}/src/jsrc/vision/traits.py +0 -0
  78. {jsrc-0.2.2 → jsrc-0.2.3}/src/jsrc.egg-info/SOURCES.txt +0 -0
  79. {jsrc-0.2.2 → jsrc-0.2.3}/src/jsrc.egg-info/dependency_links.txt +0 -0
  80. {jsrc-0.2.2 → jsrc-0.2.3}/src/jsrc.egg-info/entry_points.txt +0 -0
  81. {jsrc-0.2.2 → jsrc-0.2.3}/src/jsrc.egg-info/requires.txt +0 -0
  82. {jsrc-0.2.2 → jsrc-0.2.3}/src/jsrc.egg-info/top_level.txt +0 -0
  83. {jsrc-0.2.2 → jsrc-0.2.3}/tests/test_analyze_extra.py +0 -0
  84. {jsrc-0.2.2 → jsrc-0.2.3}/tests/test_analyze_phylo.py +0 -0
  85. {jsrc-0.2.2 → jsrc-0.2.3}/tests/test_cli_error_behavior.py +0 -0
  86. {jsrc-0.2.2 → jsrc-0.2.3}/tests/test_grn_conversion.py +0 -0
  87. {jsrc-0.2.2 → jsrc-0.2.3}/tests/test_gs_train.py +0 -0
  88. {jsrc-0.2.2 → jsrc-0.2.3}/tests/test_job_core.py +0 -0
  89. {jsrc-0.2.2 → jsrc-0.2.3}/tests/test_job_portability.py +0 -0
  90. {jsrc-0.2.2 → jsrc-0.2.3}/tests/test_plot_commands.py +0 -0
  91. {jsrc-0.2.2 → jsrc-0.2.3}/tests/test_plot_core.py +0 -0
  92. {jsrc-0.2.2 → jsrc-0.2.3}/tests/test_progress.py +0 -0
  93. {jsrc-0.2.2 → jsrc-0.2.3}/tests/test_seq_codon_kmer.py +0 -0
  94. {jsrc-0.2.2 → jsrc-0.2.3}/tests/test_seq_core.py +0 -0
  95. {jsrc-0.2.2 → jsrc-0.2.3}/tests/test_seq_digest.py +0 -0
  96. {jsrc-0.2.2 → jsrc-0.2.3}/tests/test_seq_extract.py +0 -0
  97. {jsrc-0.2.2 → jsrc-0.2.3}/tests/test_seq_fetch.py +0 -0
  98. {jsrc-0.2.2 → jsrc-0.2.3}/tests/test_seq_promoter.py +0 -0
  99. {jsrc-0.2.2 → jsrc-0.2.3}/tests/test_seq_qc.py +0 -0
  100. {jsrc-0.2.2 → jsrc-0.2.3}/tests/test_seq_rename.py +0 -0
  101. {jsrc-0.2.2 → jsrc-0.2.3}/tests/test_seq_translate.py +0 -0
  102. {jsrc-0.2.2 → jsrc-0.2.3}/tests/test_seq_window.py +0 -0
  103. {jsrc-0.2.2 → jsrc-0.2.3}/tests/test_vision_efd.py +0 -0
  104. {jsrc-0.2.2 → jsrc-0.2.3}/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.2
3
+ Version: 0.2.3
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.2"
3
+ version = "0.2.3"
4
4
  description = "Python library for bioinformatics and scientific computing"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -0,0 +1,41 @@
1
+ import importlib
2
+ from typing import Any
3
+
4
+ _SUBCOMMANDS: dict[str, tuple[str, str]] = {
5
+ "phylo": ("jsrc.analyze.phylo", "Build phylogenetic trees"),
6
+ "motif": ("jsrc.analyze.motif", "Motif discovery and reporting"),
7
+ "qc": ("jsrc.analyze.qc", "Alignment/sequence QC"),
8
+ "msa_consensus": ("jsrc.analyze.msa_consensus", "MSA consensus statistics"),
9
+ "snpindel": ("jsrc.analyze.snpindel", "SNP/INDEL analysis from alignments"),
10
+ "bootstrap_phylo": ("jsrc.analyze.bootstrap_phylo", "Bootstrap phylogeny support"),
11
+ }
12
+
13
+
14
+ def _register_stub_subcommands(subparsers: Any) -> None:
15
+ for name, (_, help_text) in _SUBCOMMANDS.items():
16
+ subparsers.add_parser(name, help=help_text)
17
+
18
+
19
+ def _register_selected_subcommand(subparsers: Any, selected: str) -> bool:
20
+ module_path, _ = _SUBCOMMANDS.get(selected, ("", ""))
21
+ if not module_path:
22
+ return False
23
+ mod = importlib.import_module(module_path)
24
+ reg = getattr(mod, "register", None)
25
+ if reg is None:
26
+ raise AttributeError(f"{module_path}: missing register")
27
+ reg(subparsers)
28
+ return True
29
+
30
+
31
+ def register_subparser(
32
+ subparsers: Any, selected_subcommand: str | None = None
33
+ ) -> None:
34
+ analyze_parser = subparsers.add_parser("analyze", help="Analysis tools")
35
+ analyze_sub = analyze_parser.add_subparsers(dest="analyze_cmd")
36
+ analyze_parser.set_defaults(_group_parser=analyze_parser)
37
+ if selected_subcommand and _register_selected_subcommand(
38
+ analyze_sub, selected_subcommand
39
+ ):
40
+ return
41
+ _register_stub_subcommands(analyze_sub)
@@ -1,5 +1,6 @@
1
1
  import argparse
2
2
  import importlib
3
+ import inspect
3
4
  import logging
4
5
  import os
5
6
  import sys
@@ -24,6 +25,16 @@ MODULES = {
24
25
  "job": "jsrc.job",
25
26
  }
26
27
 
28
+ MODULE_HELP = {
29
+ "seq": "Sequence tools",
30
+ "plot": "Visualization",
31
+ "analyze": "Analysis workflows",
32
+ "gs": "Genomic selection",
33
+ "grn": "GRN tools",
34
+ "vision": "Image analysis",
35
+ "job": "Background jobs",
36
+ }
37
+
27
38
 
28
39
  def _iter_enabled_modules() -> list[str]:
29
40
  only = [x.strip() for x in os.getenv("JSRC_MODULES", "").split(",") if x.strip()]
@@ -34,31 +45,7 @@ def _iter_enabled_modules() -> list[str]:
34
45
  return [n for n in names if n in MODULES and n not in disabled]
35
46
 
36
47
 
37
- def _register_modules(
38
- subparsers: argparse.Action, *, debug: bool = False
39
- ) -> tuple[list[str], list[str]]:
40
- loaded: list[str] = []
41
- errors: list[str] = []
42
- for name in _iter_enabled_modules():
43
- try:
44
- mod = importlib.import_module(MODULES[name])
45
- reg = getattr(mod, "register_subparser", None)
46
- if reg is None:
47
- errors.append(f"{name}: missing register_subparser")
48
- continue
49
- reg(subparsers)
50
- loaded.append(name)
51
- except (ImportError, AttributeError) as exc:
52
- if debug:
53
- raise
54
- errors.append(f"{name}: {exc}")
55
- return loaded, errors
56
-
57
-
58
- def main() -> None:
59
- debug_mode = "--debug" in sys.argv[1:]
60
- verbose = "--verbose" in sys.argv[1:] or debug_mode
61
- setup_logging(verbose=verbose)
48
+ def _build_base_parser() -> argparse.ArgumentParser:
62
49
  parser = argparse.ArgumentParser(
63
50
  prog="jsrc", description="General-purpose bioinformatics and data toolkit"
64
51
  )
@@ -69,17 +56,76 @@ def main() -> None:
69
56
  action="store_true",
70
57
  help="Show traceback for module loading and runtime errors",
71
58
  )
72
- subparsers = parser.add_subparsers(dest="command", help="Available modules")
59
+ return parser
60
+
61
+
62
+ def _register_stub_modules(
63
+ subparsers: argparse.Action, enabled_modules: list[str]
64
+ ) -> None:
65
+ for name in enabled_modules:
66
+ subparsers.add_parser(name, help=MODULE_HELP.get(name, f"{name} module"))
73
67
 
74
- loaded, errors = _register_modules(subparsers, debug=debug_mode)
75
- if errors:
76
- logging.warning("some modules failed to load:")
77
- for item in errors:
78
- logging.warning(" - %s", item)
79
- if not loaded:
68
+
69
+ def _register_one_module(
70
+ subparsers: argparse.Action,
71
+ module_name: str,
72
+ *,
73
+ selected_subcommand: str | None = None,
74
+ debug: bool = False,
75
+ ) -> bool:
76
+ try:
77
+ mod = importlib.import_module(MODULES[module_name])
78
+ reg = getattr(mod, "register_subparser", None)
79
+ if reg is None:
80
+ raise AttributeError("missing register_subparser")
81
+ params = inspect.signature(reg).parameters
82
+ if "selected_subcommand" in params:
83
+ reg(subparsers, selected_subcommand=selected_subcommand)
84
+ else:
85
+ reg(subparsers)
86
+ return True
87
+ except (ImportError, AttributeError) as exc:
88
+ if debug:
89
+ raise
90
+ logging.error("Error: failed to load module '%s': %s", module_name, exc)
91
+ return False
92
+
93
+
94
+ def _probe_route(argv: list[str]) -> tuple[str | None, str | None]:
95
+ probe = argparse.ArgumentParser(add_help=False)
96
+ probe.add_argument("--verbose", action="store_true")
97
+ probe.add_argument("--debug", action="store_true")
98
+ probe.add_argument("-v", "--version", action="store_true")
99
+ probe.add_argument("command", nargs="?")
100
+ probe.add_argument("subcommand", nargs="?")
101
+ args, _ = probe.parse_known_args(argv)
102
+ return args.command, args.subcommand
103
+
104
+
105
+ def main() -> None:
106
+ argv = sys.argv[1:]
107
+ debug_mode = "--debug" in argv
108
+ verbose = "--verbose" in sys.argv[1:] or debug_mode
109
+ setup_logging(verbose=verbose)
110
+ enabled_modules = _iter_enabled_modules()
111
+ if not enabled_modules:
80
112
  logging.error("no module loaded")
81
113
  sys.exit(2)
82
114
 
115
+ requested_module, requested_subcommand = _probe_route(argv)
116
+ parser = _build_base_parser()
117
+ subparsers = parser.add_subparsers(dest="command", help="Available modules")
118
+ if requested_module and requested_module in enabled_modules:
119
+ if not _register_one_module(
120
+ subparsers,
121
+ requested_module,
122
+ selected_subcommand=requested_subcommand,
123
+ debug=debug_mode,
124
+ ):
125
+ sys.exit(2)
126
+ else:
127
+ _register_stub_modules(subparsers, enabled_modules)
128
+
83
129
  args = parser.parse_args()
84
130
  if not args.command:
85
131
  parser.print_help()
@@ -0,0 +1,36 @@
1
+ import importlib
2
+ from typing import Any
3
+
4
+ _SUBCOMMANDS: dict[str, tuple[str, str]] = {
5
+ "build": ("jsrc.grn.build", "Build GRN viewer package"),
6
+ "net2json": ("jsrc.grn.net2json", "Convert GRN edge table to grn.json"),
7
+ "anno2json": ("jsrc.grn.anno2json", "Convert annotation table to annotation.json"),
8
+ "serve": ("jsrc.grn.serve", "Start GRN viewer service"),
9
+ "centrality": ("jsrc.grn.centrality", "Compute GRN centrality metrics"),
10
+ }
11
+
12
+
13
+ def _register_stub_subcommands(subparsers: Any) -> None:
14
+ for name, (_, help_text) in _SUBCOMMANDS.items():
15
+ subparsers.add_parser(name, help=help_text)
16
+
17
+
18
+ def _register_selected_subcommand(subparsers: Any, selected: str) -> bool:
19
+ module_path, _ = _SUBCOMMANDS.get(selected, ("", ""))
20
+ if not module_path:
21
+ return False
22
+ mod = importlib.import_module(module_path)
23
+ reg = getattr(mod, "register", None)
24
+ if reg is None:
25
+ raise AttributeError(f"{module_path}: missing register")
26
+ reg(subparsers)
27
+ return True
28
+
29
+
30
+ def register_subparser(subparsers: Any, selected_subcommand: str | None = None) -> None:
31
+ grn_parser = subparsers.add_parser("grn", help="GRN conversion and local viewer")
32
+ grn_sub = grn_parser.add_subparsers(dest="grn_cmd")
33
+ grn_parser.set_defaults(_group_parser=grn_parser)
34
+ if selected_subcommand and _register_selected_subcommand(grn_sub, selected_subcommand):
35
+ return
36
+ _register_stub_subcommands(grn_sub)
@@ -0,0 +1,36 @@
1
+ import importlib
2
+ from typing import Any
3
+
4
+ _SUBCOMMANDS: dict[str, tuple[str, str]] = {
5
+ "build": ("jsrc.gs.build", "Build genomic selection datasets"),
6
+ "split": ("jsrc.gs.split", "Split GS datasets into folds"),
7
+ "train": ("jsrc.gs.train", "Train and evaluate GS models"),
8
+ }
9
+
10
+
11
+ def _register_stub_subcommands(subparsers: Any) -> None:
12
+ for name, (_, help_text) in _SUBCOMMANDS.items():
13
+ subparsers.add_parser(name, help=help_text)
14
+
15
+
16
+ def _register_selected_subcommand(subparsers: Any, selected: str) -> bool:
17
+ module_path, _ = _SUBCOMMANDS.get(selected, ("", ""))
18
+ if not module_path:
19
+ return False
20
+ mod = importlib.import_module(module_path)
21
+ reg = getattr(mod, "register", None)
22
+ if reg is None:
23
+ raise AttributeError(f"{module_path}: missing register")
24
+ reg(subparsers)
25
+ return True
26
+
27
+
28
+ def register_subparser(subparsers: Any, selected_subcommand: str | None = None) -> None:
29
+ gs_parser = subparsers.add_parser(
30
+ "gs", help="Genomic selection dataset and model workflows"
31
+ )
32
+ gs_sub = gs_parser.add_subparsers(dest="gs_cmd")
33
+ gs_parser.set_defaults(_group_parser=gs_parser)
34
+ if selected_subcommand and _register_selected_subcommand(gs_sub, selected_subcommand):
35
+ return
36
+ _register_stub_subcommands(gs_sub)
@@ -0,0 +1,38 @@
1
+ import importlib
2
+ from typing import Any
3
+
4
+ _SUBCOMMANDS: dict[str, tuple[str, str]] = {
5
+ "submit": ("jsrc.job.submit", "Submit a background job"),
6
+ "ls": ("jsrc.job.ls", "List jobs"),
7
+ "show": ("jsrc.job.show", "Show one job"),
8
+ "logs": ("jsrc.job.logs", "Show job logs"),
9
+ "kill": ("jsrc.job.kill", "Terminate a running job"),
10
+ "history": ("jsrc.job.history", "Show job history"),
11
+ "gc": ("jsrc.job.gc", "Garbage-collect old job artifacts"),
12
+ }
13
+
14
+
15
+ def _register_stub_subcommands(subparsers: Any) -> None:
16
+ for name, (_, help_text) in _SUBCOMMANDS.items():
17
+ subparsers.add_parser(name, help=help_text)
18
+
19
+
20
+ def _register_selected_subcommand(subparsers: Any, selected: str) -> bool:
21
+ module_path, _ = _SUBCOMMANDS.get(selected, ("", ""))
22
+ if not module_path:
23
+ return False
24
+ mod = importlib.import_module(module_path)
25
+ reg = getattr(mod, "register", None)
26
+ if reg is None:
27
+ raise AttributeError(f"{module_path}: missing register")
28
+ reg(subparsers)
29
+ return True
30
+
31
+
32
+ def register_subparser(subparsers: Any, selected_subcommand: str | None = None) -> None:
33
+ job_parser = subparsers.add_parser("job", help="Track and manage background jobs")
34
+ job_sub = job_parser.add_subparsers(dest="job_cmd")
35
+ job_parser.set_defaults(_group_parser=job_parser)
36
+ if selected_subcommand and _register_selected_subcommand(job_sub, selected_subcommand):
37
+ return
38
+ _register_stub_subcommands(job_sub)
@@ -0,0 +1,42 @@
1
+ import importlib
2
+ from typing import Any
3
+
4
+ _SUBCOMMANDS: dict[str, tuple[str, str]] = {
5
+ "gene": ("jsrc.plot.gene", "Plot gene structure diagram"),
6
+ "exon": ("jsrc.plot.exon", "Plot exon structure diagram"),
7
+ "chromosome": ("jsrc.plot.chromosome", "Plot chromosome map"),
8
+ "domain": ("jsrc.plot.domain", "Plot protein domain architecture"),
9
+ "cis": ("jsrc.plot.cis", "Plot cis-regulatory elements"),
10
+ "heart": ("jsrc.plot.heart", "Plot heart curve"),
11
+ "rose": ("jsrc.plot.rose", "Plot 3D rose model"),
12
+ "dotplot": ("jsrc.plot.dotplot", "Sequence dotplot by exact k-mer matches"),
13
+ "circoslite": ("jsrc.plot.circoslite", "Simple circular tracks for genome stats"),
14
+ }
15
+
16
+
17
+ def _register_stub_subcommands(subparsers: Any) -> None:
18
+ for name, (_, help_text) in _SUBCOMMANDS.items():
19
+ subparsers.add_parser(name, help=help_text)
20
+
21
+
22
+ def _register_selected_subcommand(subparsers: Any, selected: str) -> bool:
23
+ module_path, _ = _SUBCOMMANDS.get(selected, ("", ""))
24
+ if not module_path:
25
+ return False
26
+ mod = importlib.import_module(module_path)
27
+ reg = getattr(mod, "register", None)
28
+ if reg is None:
29
+ raise AttributeError(f"{module_path}: missing register")
30
+ reg(subparsers)
31
+ return True
32
+
33
+
34
+ def register_subparser(
35
+ subparsers: Any, selected_subcommand: str | None = None
36
+ ) -> None:
37
+ plot_parser = subparsers.add_parser("plot", help="Visualization")
38
+ plot_sub = plot_parser.add_subparsers(dest="plot_cmd")
39
+ plot_parser.set_defaults(_group_parser=plot_parser)
40
+ if selected_subcommand and _register_selected_subcommand(plot_sub, selected_subcommand):
41
+ return
42
+ _register_stub_subcommands(plot_sub)
@@ -0,0 +1,82 @@
1
+ import os
2
+ import sys
3
+
4
+ import numpy as np
5
+ from argparse import Namespace
6
+ from typing import Any
7
+
8
+
9
+ def _load_interactive_pyplot() -> Any:
10
+ try:
11
+ import matplotlib
12
+ except ImportError as exc:
13
+ raise SystemExit(
14
+ "matplotlib is required for this command. Install it with: pip install matplotlib"
15
+ ) from exc
16
+
17
+ env_backend = os.getenv("MPLBACKEND", "").strip()
18
+ if env_backend:
19
+ import matplotlib.pyplot as plt
20
+
21
+ if "agg" in matplotlib.get_backend().lower():
22
+ raise SystemExit(
23
+ "jsrc plot heart requires an interactive matplotlib backend. "
24
+ "Current MPLBACKEND is non-interactive."
25
+ )
26
+ return plt
27
+
28
+ if sys.platform.startswith("linux") and not (
29
+ os.getenv("DISPLAY") or os.getenv("WAYLAND_DISPLAY")
30
+ ):
31
+ raise SystemExit(
32
+ "No graphical display detected. Set DISPLAY/WAYLAND_DISPLAY or run in a desktop session."
33
+ )
34
+
35
+ for backend in ("TkAgg", "QtAgg", "Qt5Agg", "GTK3Agg", "WXAgg", "MacOSX"):
36
+ try:
37
+ matplotlib.use(backend, force=True)
38
+ import matplotlib.pyplot as plt
39
+
40
+ if "agg" not in matplotlib.get_backend().lower():
41
+ return plt
42
+ except (ImportError, ValueError):
43
+ continue
44
+
45
+ raise SystemExit(
46
+ "No interactive matplotlib backend is available. "
47
+ "Install a GUI backend (for example python3-tk or PyQt6)."
48
+ )
49
+
50
+
51
+ def cmd(args: Namespace) -> None:
52
+ plt = _load_interactive_pyplot()
53
+ x = np.arange(-1.8, 1.8, 0.005)
54
+
55
+ plt.figure(figsize=(12, 10))
56
+ plt.grid(True)
57
+ plt.axis([-3, 3, -2, 4])
58
+
59
+ plt.text(
60
+ 0,
61
+ 3.3,
62
+ r"$f(x)=x^{\frac{2}{3}}+0.9(3.3-x^2)^{\frac{1}{2}}\sin(\alpha\pi x)$",
63
+ fontsize=28,
64
+ ha="center",
65
+ )
66
+ txt = plt.text(-0.35, 2.9, "", fontsize=26, ha="left")
67
+ (line,) = plt.plot([], [], linewidth=3.5, color="#CD5555")
68
+
69
+ for alpha in np.arange(1, 20.01, 0.01):
70
+ y = np.cbrt(x**2) + 0.9 * np.sqrt(np.clip(3.3 - x**2, 0, None)) * np.sin(
71
+ alpha * np.pi * x
72
+ )
73
+ line.set_data(x, y)
74
+ txt.set_text(rf"$\alpha={alpha:.2f}$")
75
+ plt.pause(0.003)
76
+
77
+ plt.show()
78
+
79
+
80
+ def register(subparsers: Any) -> None:
81
+ p = subparsers.add_parser("heart", help="Plot heart curve")
82
+ p.set_defaults(func=cmd)
@@ -0,0 +1,41 @@
1
+ import importlib
2
+ from typing import Any
3
+
4
+ _SUBCOMMANDS: dict[str, tuple[str, str]] = {
5
+ "extract": ("jsrc.seq.extract", "Extract sequences by IDs"),
6
+ "fetch": ("jsrc.seq.fetch", "Fetch sequences from remote databases"),
7
+ "digest": ("jsrc.seq.digest", "Simulate restriction enzyme digestion"),
8
+ "rename": ("jsrc.seq.rename", "Rename FASTA headers"),
9
+ "translate": ("jsrc.seq.translate", "Translate CDS/DNA to protein"),
10
+ "promoter": ("jsrc.seq.promoter", "Extract promoter sequences"),
11
+ "qc": ("jsrc.seq.qc", "Sequence quality statistics"),
12
+ "codon": ("jsrc.seq.codon", "Codon usage analysis"),
13
+ "kmer": ("jsrc.seq.kmer", "Count k-mer frequencies"),
14
+ "window": ("jsrc.seq.window", "Sliding-window sequence statistics"),
15
+ }
16
+
17
+
18
+ def _register_stub_subcommands(subparsers: Any) -> None:
19
+ for name, (_, help_text) in _SUBCOMMANDS.items():
20
+ subparsers.add_parser(name, help=help_text)
21
+
22
+
23
+ def _register_selected_subcommand(subparsers: Any, selected: str) -> bool:
24
+ module_path, _ = _SUBCOMMANDS.get(selected, ("", ""))
25
+ if not module_path:
26
+ return False
27
+ mod = importlib.import_module(module_path)
28
+ reg = getattr(mod, "register", None)
29
+ if reg is None:
30
+ raise AttributeError(f"{module_path}: missing register")
31
+ reg(subparsers)
32
+ return True
33
+
34
+
35
+ def register_subparser(subparsers: Any, selected_subcommand: str | None = None) -> None:
36
+ seq_parser = subparsers.add_parser("seq", help="Sequence operations")
37
+ seq_sub = seq_parser.add_subparsers(dest="seq_cmd")
38
+ seq_parser.set_defaults(_group_parser=seq_parser)
39
+ if selected_subcommand and _register_selected_subcommand(seq_sub, selected_subcommand):
40
+ return
41
+ _register_stub_subcommands(seq_sub)
@@ -0,0 +1,40 @@
1
+ import importlib
2
+ from typing import Any
3
+
4
+ _SUBCOMMANDS: dict[str, tuple[str, str]] = {
5
+ "extract": ("jsrc.vision.extract", "Extract objects from images"),
6
+ "efd": ("jsrc.vision.efd", "Elliptic Fourier descriptor analysis"),
7
+ "traits": ("jsrc.vision.traits", "Measure morphology traits"),
8
+ }
9
+
10
+
11
+ def _register_stub_subcommands(subparsers: Any) -> None:
12
+ for name, (_, help_text) in _SUBCOMMANDS.items():
13
+ subparsers.add_parser(name, help=help_text)
14
+
15
+
16
+ def _register_selected_subcommand(subparsers: Any, selected: str) -> bool:
17
+ module_path, _ = _SUBCOMMANDS.get(selected, ("", ""))
18
+ if not module_path:
19
+ return False
20
+ mod = importlib.import_module(module_path)
21
+ reg = getattr(mod, "register", None)
22
+ if reg is None:
23
+ raise AttributeError(f"{module_path}: missing register")
24
+ reg(subparsers)
25
+ return True
26
+
27
+
28
+ def register_subparser(
29
+ subparsers: Any, selected_subcommand: str | None = None
30
+ ) -> None:
31
+ vision_parser = subparsers.add_parser(
32
+ "vision", help="Image recognition and shape descriptors"
33
+ )
34
+ vision_sub = vision_parser.add_subparsers(dest="vision_cmd")
35
+ vision_parser.set_defaults(_group_parser=vision_parser)
36
+ if selected_subcommand and _register_selected_subcommand(
37
+ vision_sub, selected_subcommand
38
+ ):
39
+ return
40
+ _register_stub_subcommands(vision_sub)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jsrc
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: Python library for bioinformatics and scientific computing
5
5
  Author-email: Jiaoyuan <imjiaoyuan@gmail.com>
6
6
  License-Expression: MIT
@@ -42,6 +42,62 @@ def test_cli_debug_mode_raises_original_exception(monkeypatch):
42
42
  cli.main()
43
43
 
44
44
 
45
+ def test_cli_plot_help_imports_only_plot_module(monkeypatch):
46
+ imported: list[str] = []
47
+ real_import = cli.importlib.import_module
48
+
49
+ def _spy(name: str):
50
+ imported.append(name)
51
+ return real_import(name)
52
+
53
+ monkeypatch.setattr(cli.importlib, "import_module", _spy)
54
+ monkeypatch.setattr(sys, "argv", ["jsrc", "plot", "-h"])
55
+ with pytest.raises(SystemExit) as exc:
56
+ cli.main()
57
+ assert exc.value.code == 0
58
+ assert imported == ["jsrc.plot"]
59
+
60
+
61
+ def test_cli_root_help_does_not_import_modules(monkeypatch):
62
+ imported: list[str] = []
63
+ real_import = cli.importlib.import_module
64
+
65
+ def _spy(name: str):
66
+ imported.append(name)
67
+ return real_import(name)
68
+
69
+ monkeypatch.setattr(cli.importlib, "import_module", _spy)
70
+ monkeypatch.setattr(sys, "argv", ["jsrc", "-h"])
71
+ with pytest.raises(SystemExit) as exc:
72
+ cli.main()
73
+ assert exc.value.code == 0
74
+ assert imported == []
75
+
76
+
77
+ @pytest.mark.parametrize(
78
+ ("argv", "expected_imports"),
79
+ [
80
+ (["jsrc", "plot", "heart", "-h"], ["jsrc.plot", "jsrc.plot.heart"]),
81
+ (["jsrc", "seq", "window", "-h"], ["jsrc.seq", "jsrc.seq.window"]),
82
+ (["jsrc", "grn", "centrality", "-h"], ["jsrc.grn", "jsrc.grn.centrality"]),
83
+ ],
84
+ )
85
+ def test_cli_imports_only_selected_subcommand(monkeypatch, argv, expected_imports):
86
+ imported: list[str] = []
87
+ real_import = cli.importlib.import_module
88
+
89
+ def _spy(name: str):
90
+ imported.append(name)
91
+ return real_import(name)
92
+
93
+ monkeypatch.setattr(cli.importlib, "import_module", _spy)
94
+ monkeypatch.setattr(sys, "argv", argv)
95
+ with pytest.raises(SystemExit) as exc:
96
+ cli.main()
97
+ assert exc.value.code == 0
98
+ assert imported == expected_imports
99
+
100
+
45
101
  def test_job_submit_and_list_flow(tmp_path, capsys, monkeypatch):
46
102
  monkeypatch.setenv("XDG_DATA_HOME", str(tmp_path / "xdg"))
47
103
  monkeypatch.setenv("JSRC_JOBS_FILE", str(tmp_path / "jobs.tsv"))
@@ -1,16 +0,0 @@
1
- from typing import Any
2
-
3
- from jsrc.analyze import bootstrap_phylo, motif, msa_consensus, phylo, qc, snpindel
4
-
5
-
6
- def register_subparser(subparsers: Any) -> None:
7
- analyze_parser = subparsers.add_parser("analyze", help="Analysis tools")
8
- analyze_sub = analyze_parser.add_subparsers(dest="analyze_cmd")
9
- analyze_parser.set_defaults(_group_parser=analyze_parser)
10
-
11
- phylo.register(analyze_sub)
12
- motif.register(analyze_sub)
13
- qc.register(analyze_sub)
14
- msa_consensus.register(analyze_sub)
15
- snpindel.register(analyze_sub)
16
- bootstrap_phylo.register(analyze_sub)
@@ -1,15 +0,0 @@
1
- from typing import Any
2
-
3
- from jsrc.grn import anno2json, build, centrality, net2json, serve
4
-
5
-
6
- def register_subparser(subparsers: Any) -> None:
7
- grn_parser = subparsers.add_parser("grn", help="GRN conversion and local viewer")
8
- grn_sub = grn_parser.add_subparsers(dest="grn_cmd")
9
- grn_parser.set_defaults(_group_parser=grn_parser)
10
-
11
- build.register(grn_sub)
12
- net2json.register(grn_sub)
13
- anno2json.register(grn_sub)
14
- serve.register(grn_sub)
15
- centrality.register(grn_sub)
@@ -1,15 +0,0 @@
1
- from typing import Any
2
-
3
- from jsrc.gs import build, split, train
4
-
5
-
6
- def register_subparser(subparsers: Any) -> None:
7
- gs_parser = subparsers.add_parser(
8
- "gs", help="Genomic selection dataset and model workflows"
9
- )
10
- gs_sub = gs_parser.add_subparsers(dest="gs_cmd")
11
- gs_parser.set_defaults(_group_parser=gs_parser)
12
-
13
- build.register(gs_sub)
14
- split.register(gs_sub)
15
- train.register(gs_sub)
@@ -1,17 +0,0 @@
1
- from typing import Any
2
-
3
- from jsrc.job import gc, history, kill, logs, ls, show, submit
4
-
5
-
6
- def register_subparser(subparsers: Any) -> None:
7
- job_parser = subparsers.add_parser("job", help="Track and manage background jobs")
8
- job_sub = job_parser.add_subparsers(dest="job_cmd")
9
- job_parser.set_defaults(_group_parser=job_parser)
10
-
11
- submit.register(job_sub)
12
- ls.register(job_sub)
13
- show.register(job_sub)
14
- logs.register(job_sub)
15
- kill.register(job_sub)
16
- history.register(job_sub)
17
- gc.register(job_sub)
@@ -1,29 +0,0 @@
1
- from typing import Any
2
-
3
- from jsrc.plot import (
4
- chromosome,
5
- circoslite,
6
- cis,
7
- domain,
8
- dotplot,
9
- exon,
10
- gene,
11
- heart,
12
- rose,
13
- )
14
-
15
-
16
- def register_subparser(subparsers: Any) -> None:
17
- plot_parser = subparsers.add_parser("plot", help="Visualization")
18
- plot_sub = plot_parser.add_subparsers(dest="plot_cmd")
19
- plot_parser.set_defaults(_group_parser=plot_parser)
20
-
21
- gene.register(plot_sub)
22
- exon.register(plot_sub)
23
- chromosome.register(plot_sub)
24
- domain.register(plot_sub)
25
- cis.register(plot_sub)
26
- heart.register(plot_sub)
27
- rose.register(plot_sub)
28
- dotplot.register(plot_sub)
29
- circoslite.register(plot_sub)
@@ -1,37 +0,0 @@
1
- import numpy as np
2
- import matplotlib.pyplot as plt
3
- from argparse import Namespace
4
- from typing import Any
5
-
6
-
7
- def cmd(args: Namespace) -> None:
8
- x = np.arange(-1.8, 1.8, 0.005)
9
-
10
- plt.figure(figsize=(12, 10))
11
- plt.grid(True)
12
- plt.axis([-3, 3, -2, 4])
13
-
14
- plt.text(
15
- 0,
16
- 3.3,
17
- r"$f(x)=x^{\frac{2}{3}}+0.9(3.3-x^2)^{\frac{1}{2}}\sin(\alpha\pi x)$",
18
- fontsize=28,
19
- ha="center",
20
- )
21
- txt = plt.text(-0.35, 2.9, "", fontsize=26, ha="left")
22
- (line,) = plt.plot([], [], linewidth=3.5, color="#CD5555")
23
-
24
- for alpha in np.arange(1, 20.01, 0.01):
25
- y = np.cbrt(x**2) + 0.9 * np.sqrt(np.clip(3.3 - x**2, 0, None)) * np.sin(
26
- alpha * np.pi * x
27
- )
28
- line.set_data(x, y)
29
- txt.set_text(rf"$\alpha={alpha:.2f}$")
30
- plt.pause(0.003)
31
-
32
- plt.show()
33
-
34
-
35
- def register(subparsers: Any) -> None:
36
- p = subparsers.add_parser("heart", help="Plot heart curve")
37
- p.set_defaults(func=cmd)
@@ -1,31 +0,0 @@
1
- from typing import Any
2
-
3
- from jsrc.seq import (
4
- codon,
5
- digest,
6
- extract,
7
- fetch,
8
- kmer,
9
- promoter,
10
- qc,
11
- rename,
12
- translate,
13
- window,
14
- )
15
-
16
-
17
- def register_subparser(subparsers: Any) -> None:
18
- seq_parser = subparsers.add_parser("seq", help="Sequence operations")
19
- seq_sub = seq_parser.add_subparsers(dest="seq_cmd")
20
- seq_parser.set_defaults(_group_parser=seq_parser)
21
-
22
- extract.register(seq_sub)
23
- fetch.register(seq_sub)
24
- digest.register(seq_sub)
25
- rename.register(seq_sub)
26
- translate.register(seq_sub)
27
- promoter.register(seq_sub)
28
- qc.register(seq_sub)
29
- codon.register(seq_sub)
30
- kmer.register(seq_sub)
31
- window.register(seq_sub)
@@ -1,15 +0,0 @@
1
- from typing import Any
2
-
3
- from jsrc.vision import efd, extract, traits
4
-
5
-
6
- def register_subparser(subparsers: Any) -> None:
7
- vision_parser = subparsers.add_parser(
8
- "vision", help="Image recognition and shape descriptors"
9
- )
10
- vision_sub = vision_parser.add_subparsers(dest="vision_cmd")
11
- vision_parser.set_defaults(_group_parser=vision_parser)
12
-
13
- extract.register(vision_sub)
14
- efd.register(vision_sub)
15
- traits.register(vision_sub)
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
File without changes
File without changes