cg 76.0.0__py3-none-any.whl → 83.14.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. cg/__init__.py +1 -1
  2. cg/apps/housekeeper/hk.py +18 -1
  3. cg/apps/tb/api.py +42 -5
  4. cg/cli/transfer.py +13 -2
  5. cg/cli/upload/mutacc.py +16 -3
  6. cg/cli/upload/scout.py +2 -2
  7. cg/cli/upload/utils.py +10 -1
  8. cg/cli/workflow/balsamic/base.py +86 -172
  9. cg/cli/workflow/balsamic/options.py +3 -48
  10. cg/cli/workflow/balsamic/umi.py +210 -15
  11. cg/cli/workflow/microsalt/base.py +4 -2
  12. cg/cli/workflow/mip_dna/base.py +1 -1
  13. cg/cli/workflow/nallo/base.py +73 -23
  14. cg/cli/workflow/nf_analysis.py +5 -207
  15. cg/cli/workflow/raredisease/base.py +41 -54
  16. cg/cli/workflow/rnafusion/base.py +38 -8
  17. cg/cli/workflow/taxprofiler/base.py +31 -18
  18. cg/cli/workflow/tomte/base.py +83 -10
  19. cg/constants/constants.py +25 -30
  20. cg/constants/devices.py +6 -1
  21. cg/constants/gene_panel.py +3 -1
  22. cg/constants/housekeeper_tags.py +28 -28
  23. cg/constants/lims.py +4 -0
  24. cg/constants/nf_analysis.py +0 -1
  25. cg/constants/observations.py +21 -5
  26. cg/constants/orderforms.py +3 -3
  27. cg/constants/pacbio.py +1 -0
  28. cg/constants/priority.py +1 -1
  29. cg/constants/report.py +1 -0
  30. cg/constants/scout.py +12 -9
  31. cg/constants/sequencing.py +2 -2
  32. cg/constants/tb.py +5 -5
  33. cg/exc.py +27 -5
  34. cg/meta/compress/compress.py +7 -2
  35. cg/meta/delivery_report/balsamic.py +3 -1
  36. cg/meta/delivery_report/delivery_report_api.py +4 -3
  37. cg/meta/delivery_report/nallo.py +11 -11
  38. cg/meta/delivery_report/raredisease.py +7 -3
  39. cg/meta/delivery_report/templates/macros/data_analysis/qc_metrics/balsamic_qc_metrics.html +1 -0
  40. cg/meta/delivery_report/templates/macros/ticket_system.html +1 -1
  41. cg/meta/observations/balsamic_observations_api.py +110 -14
  42. cg/meta/observations/mip_dna_observations_api.py +1 -1
  43. cg/meta/observations/nallo_observations_api.py +1 -1
  44. cg/meta/observations/observations_api.py +23 -32
  45. cg/meta/observations/raredisease_observations_api.py +1 -1
  46. cg/meta/tar/tar.py +5 -2
  47. cg/meta/transfer/lims.py +32 -3
  48. cg/meta/upload/balsamic/balsamic.py +1 -8
  49. cg/meta/upload/coverage.py +5 -5
  50. cg/meta/upload/raredisease/raredisease.py +3 -0
  51. cg/meta/upload/scout/hk_tags.py +1 -0
  52. cg/meta/upload/scout/nallo_config_builder.py +31 -7
  53. cg/meta/workflow/balsamic.py +70 -36
  54. cg/meta/workflow/fastq.py +8 -0
  55. cg/meta/workflow/microsalt/quality_controller/models.py +0 -2
  56. cg/meta/workflow/microsalt/quality_controller/quality_controller.py +8 -16
  57. cg/meta/workflow/microsalt/quality_controller/result_logger.py +3 -6
  58. cg/meta/workflow/microsalt/quality_controller/utils.py +2 -45
  59. cg/meta/workflow/nallo.py +21 -99
  60. cg/meta/workflow/nf_analysis.py +12 -263
  61. cg/meta/workflow/raredisease.py +3 -112
  62. cg/meta/workflow/rnafusion.py +2 -34
  63. cg/meta/workflow/taxprofiler.py +2 -38
  64. cg/meta/workflow/tomte.py +2 -42
  65. cg/models/balsamic/config.py +0 -24
  66. cg/models/balsamic/metrics.py +5 -3
  67. cg/models/cg_config.py +39 -16
  68. cg/models/deliverables/metric_deliverables.py +1 -1
  69. cg/models/delivery_report/metadata.py +2 -1
  70. cg/models/nallo/nallo.py +14 -64
  71. cg/models/nf_analysis.py +1 -41
  72. cg/models/raredisease/raredisease.py +1 -63
  73. cg/models/rnafusion/rnafusion.py +0 -26
  74. cg/models/scout/scout_load_config.py +5 -2
  75. cg/models/taxprofiler/taxprofiler.py +0 -42
  76. cg/models/tomte/tomte.py +0 -69
  77. cg/resources/nallo_bundle_filenames.yaml +292 -22
  78. cg/resources/raredisease_bundle_filenames.yaml +11 -1
  79. cg/resources/taxprofiler_bundle_filenames.yaml +20 -0
  80. cg/server/admin.py +106 -25
  81. cg/server/app.py +15 -4
  82. cg/server/endpoints/sequencing_run/dtos.py +21 -3
  83. cg/server/endpoints/sequencing_run/pacbio_sequencing_run.py +29 -10
  84. cg/server/endpoints/sequencing_run/pacbio_smrt_cell_metrics.py +20 -0
  85. cg/services/analysis_starter/{service.py → analysis_starter.py} +11 -9
  86. cg/services/analysis_starter/configurator/abstract_model.py +8 -0
  87. cg/services/analysis_starter/configurator/configurator.py +1 -1
  88. cg/services/analysis_starter/configurator/extensions/nallo.py +27 -0
  89. cg/services/analysis_starter/configurator/extensions/{abstract.py → pipeline_extension.py} +1 -1
  90. cg/services/analysis_starter/configurator/extensions/raredisease.py +3 -1
  91. cg/services/analysis_starter/configurator/extensions/tomte_extension.py +28 -0
  92. cg/services/analysis_starter/configurator/file_creators/balsamic_config.py +240 -0
  93. cg/services/analysis_starter/configurator/file_creators/gene_panel.py +10 -5
  94. cg/services/analysis_starter/configurator/file_creators/nextflow/params_file/abstract.py +2 -1
  95. cg/services/analysis_starter/configurator/file_creators/nextflow/params_file/models.py +40 -1
  96. cg/services/analysis_starter/configurator/file_creators/nextflow/params_file/nallo.py +37 -0
  97. cg/services/analysis_starter/configurator/file_creators/nextflow/params_file/raredisease.py +8 -5
  98. cg/services/analysis_starter/configurator/file_creators/nextflow/params_file/tomte_params_file_creator.py +64 -0
  99. cg/services/analysis_starter/configurator/file_creators/nextflow/sample_sheet/creator.py +1 -1
  100. cg/services/analysis_starter/configurator/file_creators/nextflow/sample_sheet/nallo_sample_sheet_creator.py +65 -0
  101. cg/services/analysis_starter/configurator/file_creators/nextflow/sample_sheet/protocol.py +12 -0
  102. cg/services/analysis_starter/configurator/file_creators/nextflow/sample_sheet/{raredisease.py → raredisease_sample_sheet_creator.py} +2 -2
  103. cg/services/analysis_starter/configurator/file_creators/nextflow/sample_sheet/{rnafusion.py → rnafusion_sample_sheet_creator.py} +2 -2
  104. cg/services/analysis_starter/configurator/file_creators/nextflow/sample_sheet/{taxprofiler.py → taxprofiler_sample_sheet_creator.py} +2 -2
  105. cg/services/analysis_starter/configurator/file_creators/nextflow/sample_sheet/tomte_sample_sheet_creator.py +36 -0
  106. cg/services/analysis_starter/configurator/implementations/balsamic.py +68 -0
  107. cg/services/analysis_starter/configurator/implementations/nextflow.py +22 -5
  108. cg/services/analysis_starter/configurator/models/balsamic.py +152 -0
  109. cg/services/analysis_starter/configurator/models/mip_dna.py +6 -8
  110. cg/services/analysis_starter/configurator/models/nextflow.py +9 -0
  111. cg/services/analysis_starter/constants.py +2 -0
  112. cg/services/analysis_starter/factories/configurator_factory.py +131 -51
  113. cg/services/analysis_starter/factories/starter_factory.py +36 -7
  114. cg/services/analysis_starter/input_fetcher/implementations/bam_fetcher.py +57 -0
  115. cg/services/analysis_starter/input_fetcher/implementations/fastq_fetcher.py +3 -3
  116. cg/services/analysis_starter/submitters/seqera_platform/{client.py → seqera_platform_client.py} +19 -3
  117. cg/services/analysis_starter/submitters/seqera_platform/seqera_platform_submitter.py +73 -0
  118. cg/services/analysis_starter/submitters/submitter.py +1 -1
  119. cg/services/analysis_starter/submitters/subprocess/submitter.py +2 -1
  120. cg/services/analysis_starter/tracker/implementations/balsamic.py +22 -0
  121. cg/services/analysis_starter/tracker/implementations/microsalt.py +4 -4
  122. cg/services/analysis_starter/tracker/implementations/mip_dna.py +4 -1
  123. cg/services/analysis_starter/tracker/implementations/{nextflow.py → nextflow_tracker.py} +6 -4
  124. cg/services/analysis_starter/tracker/tracker.py +19 -15
  125. cg/services/deliver_files/factory.py +1 -1
  126. cg/services/delivery_message/messages/__init__.py +24 -14
  127. cg/services/delivery_message/messages/{microsalt_mwr_message.py → microsalt_message.py} +1 -1
  128. cg/services/delivery_message/utils.py +4 -40
  129. cg/services/illumina/backup/backup_service.py +29 -7
  130. cg/services/orders/validation/constants.py +3 -0
  131. cg/services/orders/validation/index_sequences.py +558 -0
  132. cg/services/orders/validation/order_types/microsalt/models/sample.py +2 -3
  133. cg/services/run_devices/pacbio/data_storage_service/pacbio_store_service.py +39 -18
  134. cg/services/run_devices/pacbio/data_transfer_service/data_transfer_service.py +8 -2
  135. cg/services/run_devices/pacbio/data_transfer_service/dto.py +9 -3
  136. cg/services/run_devices/pacbio/data_transfer_service/utils.py +14 -7
  137. cg/services/run_devices/pacbio/metrics_parser/models.py +1 -0
  138. cg/services/run_devices/pacbio/sequencing_runs_service.py +35 -7
  139. cg/services/sequencing_qc_service/quality_checks/checks.py +18 -16
  140. cg/services/sequencing_qc_service/quality_checks/utils.py +82 -18
  141. cg/services/sequencing_qc_service/sequencing_qc_service.py +12 -10
  142. cg/store/crud/create.py +73 -42
  143. cg/store/crud/read.py +73 -7
  144. cg/store/crud/update.py +14 -3
  145. cg/store/models.py +98 -35
  146. cg/store/store.py +8 -1
  147. {cg-76.0.0.dist-info → cg-83.14.0.dist-info}/METADATA +1 -1
  148. {cg-76.0.0.dist-info → cg-83.14.0.dist-info}/RECORD +150 -138
  149. cg/services/analysis_starter/submitters/seqera_platform/submitter.py +0 -39
  150. cg/services/delivery_message/messages/microsalt_mwx_message.py +0 -18
  151. {cg-76.0.0.dist-info → cg-83.14.0.dist-info}/WHEEL +0 -0
  152. {cg-76.0.0.dist-info → cg-83.14.0.dist-info}/entry_points.txt +0 -0
@@ -1,25 +1,65 @@
1
1
  """CLI support to create config and/or start BALSAMIC """
2
2
 
3
3
  import logging
4
+ import traceback
4
5
 
5
6
  import rich_click as click
6
7
 
7
8
  from cg.cli.utils import CLICK_CONTEXT_SETTINGS
8
- from cg.cli.workflow.balsamic.base import (
9
- config_case,
10
- report_deliver,
11
- run,
12
- start,
13
- start_available,
14
- store,
15
- store_available,
16
- store_housekeeper,
17
- )
18
- from cg.cli.workflow.commands import link, resolve_compression
9
+ from cg.cli.workflow.balsamic.base import report_deliver, store, store_available, store_housekeeper
10
+ from cg.cli.workflow.balsamic.options import OPTION_PANEL_BED, OPTION_WORKFLOW_PROFILE
11
+ from cg.cli.workflow.commands import ARGUMENT_CASE_ID, link, resolve_compression
12
+ from cg.constants import EXIT_FAIL, EXIT_SUCCESS, Sex
13
+ from cg.constants.cli_options import DRY_RUN, LIMIT
14
+ from cg.constants.constants import GenomeVersion
15
+ from cg.constants.priority import SlurmQos
16
+ from cg.exc import AnalysisNotReadyError, CgError
19
17
  from cg.meta.workflow.balsamic_umi import BalsamicUmiAnalysisAPI
18
+ from cg.models.cg_config import CGConfig
19
+ from cg.store.models import Case
20
20
 
21
21
  LOG = logging.getLogger(__name__)
22
22
 
23
+ OPTION_GENDER = click.option(
24
+ "--gender",
25
+ type=click.Choice([Sex.FEMALE, Sex.MALE]),
26
+ required=False,
27
+ help="Case associated gender. Set this option to override the one selected by the customer in StatusDB.",
28
+ )
29
+ OPTION_GENOME_VERSION = click.option(
30
+ "--genome-version",
31
+ show_default=True,
32
+ default=GenomeVersion.HG19,
33
+ type=click.Choice([GenomeVersion.HG19, GenomeVersion.HG38, GenomeVersion.CANFAM3]),
34
+ help="Type and build version of the reference genome. Set this option to override the default.",
35
+ )
36
+ OPTION_QOS = click.option(
37
+ "-qos",
38
+ "--slurm-quality-of-service",
39
+ type=click.Choice([SlurmQos.LOW, SlurmQos.NORMAL, SlurmQos.HIGH, SlurmQos.EXPRESS]),
40
+ help="Job priority in SLURM. Setting this option will override the StatusDB case priority.",
41
+ )
42
+ OPTION_PON_CNN = click.option(
43
+ "--pon-cnn",
44
+ type=click.Path(exists=True),
45
+ required=False,
46
+ help="Panel of normal reference (.cnn) for CNVkit",
47
+ )
48
+ OPTION_OBSERVATIONS = click.option(
49
+ "--observations",
50
+ type=click.Path(exists=True),
51
+ multiple=True,
52
+ required=False,
53
+ help="VCF paths of clinical and/or cancer SNVs and SVs observations (WHOLE_GENOME_SEQUENCING analysis only). Set this option to "
54
+ "override the latest Loqusdb dump files.",
55
+ )
56
+ OPTION_CACHE_VERSION = click.option(
57
+ "--cache-version",
58
+ type=click.STRING,
59
+ required=False,
60
+ help="Cache version to be used for init or analysis. Use 'develop' or 'X.X.X'.",
61
+ )
62
+
23
63
 
24
64
  @click.group(
25
65
  "balsamic-umi",
@@ -36,13 +76,168 @@ def balsamic_umi(context: click.Context):
36
76
  context.obj.meta_apis["analysis_api"] = BalsamicUmiAnalysisAPI(config=config)
37
77
 
38
78
 
79
+ @balsamic_umi.command("config-case")
80
+ @ARGUMENT_CASE_ID
81
+ @OPTION_GENDER
82
+ @OPTION_GENOME_VERSION
83
+ @OPTION_PANEL_BED
84
+ @OPTION_PON_CNN
85
+ @OPTION_OBSERVATIONS
86
+ @OPTION_CACHE_VERSION
87
+ @DRY_RUN
88
+ @click.pass_obj
89
+ def config_case(
90
+ context: CGConfig,
91
+ case_id: str,
92
+ gender: str,
93
+ genome_version: str,
94
+ panel_bed: str,
95
+ pon_cnn: click.Path,
96
+ observations: list[click.Path],
97
+ cache_version: str,
98
+ dry_run: bool,
99
+ ):
100
+ """Create config file for BALSAMIC UMI analysis for a given CASE_ID."""
101
+
102
+ analysis_api: BalsamicUmiAnalysisAPI = context.meta_apis["analysis_api"]
103
+ try:
104
+ LOG.info(f"Creating config file for {case_id}.")
105
+ analysis_api.status_db.verify_case_exists(case_internal_id=case_id)
106
+ analysis_api.config_case(
107
+ case_id=case_id,
108
+ gender=gender,
109
+ genome_version=genome_version,
110
+ panel_bed=panel_bed,
111
+ pon_cnn=pon_cnn,
112
+ observations=observations,
113
+ cache_version=cache_version,
114
+ dry_run=dry_run,
115
+ )
116
+ except CgError as error:
117
+ error_info = f"Error: {type(error).__name__}: {str(error)}\n{traceback.format_exc()}"
118
+ LOG.error(f"Could not create config: {error_info}")
119
+ raise click.Abort()
120
+ except Exception as error:
121
+ error_info = f"Error: {type(error).__name__}: {str(error)}\n{traceback.format_exc()}"
122
+ LOG.error(f"Could not create config: {error_info}")
123
+ raise click.Abort()
124
+
125
+
126
+ @balsamic_umi.command("run")
127
+ @ARGUMENT_CASE_ID
128
+ @OPTION_WORKFLOW_PROFILE
129
+ @DRY_RUN
130
+ @OPTION_QOS
131
+ @click.pass_obj
132
+ def run(
133
+ context: CGConfig,
134
+ case_id: str,
135
+ workflow_profile: click.Path,
136
+ slurm_quality_of_service: str,
137
+ dry_run: bool,
138
+ ):
139
+ """Run Balsamic UMI analysis for given CASE ID"""
140
+ analysis_api: BalsamicUmiAnalysisAPI = context.meta_apis["analysis_api"]
141
+ try:
142
+ analysis_api.status_db.verify_case_exists(case_internal_id=case_id)
143
+ analysis_api.verify_case_config_file_exists(case_id=case_id, dry_run=dry_run)
144
+ analysis_api.check_analysis_ongoing(case_id)
145
+ analysis_api.run_analysis(
146
+ case_id=case_id,
147
+ workflow_profile=workflow_profile,
148
+ slurm_quality_of_service=slurm_quality_of_service,
149
+ dry_run=dry_run,
150
+ )
151
+ if dry_run:
152
+ return
153
+ analysis_api.on_analysis_started(case_id)
154
+ except Exception as error:
155
+ error_info = f"Error: {type(error).__name__}: {str(error)}\n{traceback.format_exc()}"
156
+ LOG.error(f"Could not run analysis: {error_info}")
157
+ raise click.Abort()
158
+
159
+
160
+ @balsamic_umi.command("start")
161
+ @ARGUMENT_CASE_ID
162
+ @OPTION_GENDER
163
+ @OPTION_GENOME_VERSION
164
+ @OPTION_QOS
165
+ @DRY_RUN
166
+ @OPTION_PANEL_BED
167
+ @OPTION_PON_CNN
168
+ @OPTION_CACHE_VERSION
169
+ @OPTION_OBSERVATIONS
170
+ @OPTION_WORKFLOW_PROFILE
171
+ @click.pass_context
172
+ def start(
173
+ context: click.Context,
174
+ case_id: str,
175
+ gender: str,
176
+ genome_version: str,
177
+ cache_version: str,
178
+ panel_bed: str,
179
+ pon_cnn: str,
180
+ observations: list[click.Path],
181
+ slurm_quality_of_service: str,
182
+ workflow_profile: click.Path,
183
+ dry_run: bool,
184
+ ):
185
+ """Start full workflow for case ID."""
186
+ analysis_api: BalsamicUmiAnalysisAPI = context.obj.meta_apis["analysis_api"]
187
+ analysis_api.prepare_fastq_files(case_id=case_id, dry_run=dry_run)
188
+ LOG.info(f"Starting analysis for {case_id}")
189
+ context.invoke(link, case_id=case_id, dry_run=dry_run)
190
+ context.invoke(
191
+ config_case,
192
+ case_id=case_id,
193
+ gender=gender,
194
+ genome_version=genome_version,
195
+ cache_version=cache_version,
196
+ panel_bed=panel_bed,
197
+ pon_cnn=pon_cnn,
198
+ observations=observations,
199
+ dry_run=dry_run,
200
+ )
201
+ context.invoke(
202
+ run,
203
+ case_id=case_id,
204
+ workflow_profile=workflow_profile,
205
+ slurm_quality_of_service=slurm_quality_of_service,
206
+ dry_run=dry_run,
207
+ )
208
+
209
+
210
+ @balsamic_umi.command("start-available")
211
+ @DRY_RUN
212
+ @LIMIT
213
+ @click.pass_context
214
+ def start_available(context: click.Context, dry_run: bool = False, limit: int | None = None):
215
+ """Start full workflow for all cases ready for analysis"""
216
+
217
+ analysis_api: BalsamicUmiAnalysisAPI = context.obj.meta_apis["analysis_api"]
218
+
219
+ cases: list[Case] = analysis_api.get_cases_to_analyze(limit=limit)
220
+ LOG.info(f"Starting {len(cases)} available Balsamic UMI cases")
221
+
222
+ exit_code: int = EXIT_SUCCESS
223
+ for case in cases:
224
+ try:
225
+ context.invoke(start, case_id=case.internal_id, dry_run=dry_run)
226
+ except AnalysisNotReadyError as error:
227
+ LOG.error(error)
228
+ except CgError as error:
229
+ LOG.error(error)
230
+ exit_code = EXIT_FAIL
231
+ except Exception as error:
232
+ LOG.error(f"Unspecified error occurred: {error}")
233
+ exit_code = EXIT_FAIL
234
+ if exit_code:
235
+ raise click.Abort
236
+
237
+
39
238
  balsamic_umi.add_command(resolve_compression)
40
239
  balsamic_umi.add_command(link)
41
- balsamic_umi.add_command(config_case)
42
- balsamic_umi.add_command(run)
43
240
  balsamic_umi.add_command(report_deliver)
44
241
  balsamic_umi.add_command(store_housekeeper)
45
- balsamic_umi.add_command(start)
46
- balsamic_umi.add_command(start_available)
47
242
  balsamic_umi.add_command(store)
48
243
  balsamic_umi.add_command(store_available)
@@ -12,12 +12,12 @@ from cg.constants.constants import Workflow
12
12
  from cg.meta.workflow.analysis import AnalysisAPI
13
13
  from cg.meta.workflow.microsalt import MicrosaltAnalysisAPI
14
14
  from cg.models.cg_config import CGConfig
15
+ from cg.services.analysis_starter.analysis_starter import AnalysisStarter
15
16
  from cg.services.analysis_starter.configurator.implementations.microsalt import (
16
17
  MicrosaltConfigurator,
17
18
  )
18
19
  from cg.services.analysis_starter.factories.configurator_factory import ConfiguratorFactory
19
20
  from cg.services.analysis_starter.factories.starter_factory import AnalysisStarterFactory
20
- from cg.services.analysis_starter.service import AnalysisStarter
21
21
 
22
22
  LOG = logging.getLogger(__name__)
23
23
 
@@ -41,7 +41,9 @@ microsalt.add_command(resolve_compression)
41
41
  @ARGUMENT_CASE_ID
42
42
  @click.pass_obj
43
43
  def config_case(cg_config: CGConfig, case_id: str) -> None:
44
- """Create a config file for a microSALT case."""
44
+ """
45
+ Configure a microSALT case so that it is ready to be run.
46
+ """
45
47
  factory = ConfiguratorFactory(cg_config)
46
48
  configurator = cast(MicrosaltConfigurator, factory.get_configurator(Workflow.MICROSALT))
47
49
  configurator.configure(case_id=case_id)
@@ -24,10 +24,10 @@ from cg.constants import Workflow
24
24
  from cg.meta.workflow.analysis import AnalysisAPI
25
25
  from cg.meta.workflow.mip_dna import MipDNAAnalysisAPI
26
26
  from cg.models.cg_config import CGConfig
27
+ from cg.services.analysis_starter.analysis_starter import AnalysisStarter
27
28
  from cg.services.analysis_starter.configurator.implementations.mip_dna import MIPDNAConfigurator
28
29
  from cg.services.analysis_starter.factories.configurator_factory import ConfiguratorFactory
29
30
  from cg.services.analysis_starter.factories.starter_factory import AnalysisStarterFactory
30
- from cg.services.analysis_starter.service import AnalysisStarter
31
31
 
32
32
  LOG = logging.getLogger(__name__)
33
33
 
@@ -1,28 +1,29 @@
1
1
  """CLI support to create config and/or start NALLO."""
2
2
 
3
3
  import logging
4
+ from typing import cast
4
5
 
5
6
  import rich_click as click
6
7
 
7
- from cg.cli.utils import CLICK_CONTEXT_SETTINGS, echo_lines
8
+ from cg.cli.utils import CLICK_CONTEXT_SETTINGS
8
9
  from cg.cli.workflow.commands import ARGUMENT_CASE_ID
9
- from cg.constants.cli_options import DRY_RUN
10
10
  from cg.cli.workflow.nf_analysis import (
11
- config_case,
11
+ OPTION_RESUME,
12
+ OPTION_REVISION,
12
13
  metrics_deliver,
13
14
  report_deliver,
14
- run,
15
- start,
16
- start_available,
17
15
  store,
18
16
  store_available,
19
17
  store_housekeeper,
20
18
  )
21
-
22
- from cg.constants.constants import MetaApis
19
+ from cg.constants.constants import MetaApis, Workflow
23
20
  from cg.meta.workflow.analysis import AnalysisAPI
24
21
  from cg.meta.workflow.nallo import NalloAnalysisAPI
25
22
  from cg.models.cg_config import CGConfig
23
+ from cg.services.analysis_starter.analysis_starter import AnalysisStarter
24
+ from cg.services.analysis_starter.configurator.implementations.nextflow import NextflowConfigurator
25
+ from cg.services.analysis_starter.factories.configurator_factory import ConfiguratorFactory
26
+ from cg.services.analysis_starter.factories.starter_factory import AnalysisStarterFactory
26
27
 
27
28
  LOG = logging.getLogger(__name__)
28
29
 
@@ -35,29 +36,78 @@ def nallo(context: click.Context) -> None:
35
36
  context.obj.meta_apis[MetaApis.ANALYSIS_API] = NalloAnalysisAPI(config=context.obj)
36
37
 
37
38
 
38
- nallo.add_command(config_case)
39
39
  nallo.add_command(report_deliver)
40
- nallo.add_command(run)
41
- nallo.add_command(start)
42
- nallo.add_command(start_available)
43
40
  nallo.add_command(store)
44
41
  nallo.add_command(store_available)
45
42
  nallo.add_command(store_housekeeper)
46
43
  nallo.add_command(metrics_deliver)
47
44
 
48
45
 
49
- @nallo.command("panel")
50
- @DRY_RUN
46
+ @nallo.command("config-case")
51
47
  @ARGUMENT_CASE_ID
52
48
  @click.pass_obj
53
- def panel(context: CGConfig, case_id: str, dry_run: bool) -> None:
54
- """Write aggregated gene panel file exported from Scout."""
49
+ def config_case(cg_config: CGConfig, case_id: str):
50
+ """
51
+ Configure a Nallo case so that it is ready to be run.
52
+
53
+ \b
54
+ Creates the following files in the case run directory:
55
+ - CASE_ID_params_file.yaml
56
+ - CASE_ID_nextflow_config.json
57
+ - CASE_ID_samplesheet.csv
58
+ """
59
+ factory = ConfiguratorFactory(cg_config)
60
+ configurator = cast(NextflowConfigurator, factory.get_configurator(Workflow.NALLO))
61
+ configurator.configure(case_id=case_id)
62
+
63
+
64
+ @nallo.command("run")
65
+ @OPTION_REVISION
66
+ @OPTION_RESUME
67
+ @ARGUMENT_CASE_ID
68
+ @click.pass_obj
69
+ def run(cg_config: CGConfig, case_id: str, resume: bool, revision: str | None) -> None:
70
+ """
71
+ Run a preconfigured Nallo case.
72
+
73
+ \b
74
+ Assumes that the following files exist in the case run directory:
75
+ - CASE_ID_params_file.yaml
76
+ - CASE_ID_nextflow_config.json
77
+ - CASE_ID_samplesheet.csv
78
+ """
79
+ factory = AnalysisStarterFactory(cg_config)
80
+ analysis_starter: AnalysisStarter = factory.get_analysis_starter_for_workflow(Workflow.NALLO)
81
+ analysis_starter.run(case_id=case_id, resume=resume, revision=revision)
55
82
 
56
- analysis_api: NalloAnalysisAPI = context.meta_apis["analysis_api"]
57
- analysis_api.status_db.verify_case_exists(case_internal_id=case_id)
58
83
 
59
- bed_lines: list[str] = analysis_api.get_gene_panel(case_id=case_id)
60
- if dry_run:
61
- echo_lines(lines=bed_lines)
62
- return
63
- analysis_api.write_panel_as_tsv(case_id=case_id, content=bed_lines)
84
+ @nallo.command("start")
85
+ @OPTION_REVISION
86
+ @ARGUMENT_CASE_ID
87
+ @click.pass_obj
88
+ def start(cg_config: CGConfig, case_id: str, revision: str | None):
89
+ """
90
+ Start a Nallo case.
91
+
92
+ \b
93
+ Configures the case and writes the following files:
94
+ - CASE_ID_params_file.yaml
95
+ - CASE_ID_nextflow_config.json
96
+ - CASE_ID_samplesheet.csv
97
+ and submits the job to the Seqera Platform.
98
+ """
99
+ factory = AnalysisStarterFactory(cg_config)
100
+ analysis_starter: AnalysisStarter = factory.get_analysis_starter_for_workflow(Workflow.NALLO)
101
+ analysis_starter.start(case_id=case_id, revision=revision)
102
+
103
+
104
+ @nallo.command("start-available")
105
+ @click.pass_obj
106
+ def start_available(cg_config: CGConfig):
107
+ """Starts all available Nallo cases."""
108
+ LOG.info("Starting Nallo workflow for all available cases.")
109
+ factory = AnalysisStarterFactory(cg_config)
110
+ analysis_starter: AnalysisStarter = factory.get_analysis_starter_for_workflow(Workflow.NALLO)
111
+ succeeded: bool = analysis_starter.start_available()
112
+ if not succeeded:
113
+ raise click.Abort
@@ -3,232 +3,30 @@
3
3
  import logging
4
4
 
5
5
  import rich_click as click
6
- from pydantic import ValidationError
7
6
 
8
7
  from cg.cli.workflow.commands import ARGUMENT_CASE_ID
9
8
  from cg.cli.workflow.utils import validate_force_store_option
10
- from cg.constants import EXIT_FAIL, EXIT_SUCCESS, Workflow
11
9
  from cg.constants.cli_options import COMMENT, DRY_RUN, FORCE
12
10
  from cg.constants.constants import MetaApis
13
- from cg.exc import AnalysisNotReadyError, CgError, HousekeeperStoreError
11
+ from cg.exc import CgError, HousekeeperStoreError
14
12
  from cg.meta.workflow.nf_analysis import NfAnalysisAPI
15
13
  from cg.models.cg_config import CGConfig
16
- from cg.store.models import Case
17
14
 
18
15
  LOG = logging.getLogger(__name__)
19
16
 
20
- OPTION_WORKDIR = click.option(
21
- "--work-dir",
22
- type=click.Path(),
23
- help="Directory where intermediate result files are stored",
24
- )
17
+
25
18
  OPTION_RESUME = click.option(
26
19
  "--resume",
27
- is_flag=True,
28
- default=False,
29
- show_default=True,
30
- help="Execute the script using the cached results, useful to continue \
31
- executions that was stopped by an error",
32
- )
33
- OPTION_PROFILE = click.option(
34
- "--profile",
35
- type=str,
20
+ default=True,
36
21
  show_default=True,
37
- help="Choose a configuration profile",
38
- )
39
-
40
- OPTION_CONFIG = click.option(
41
- "--config",
42
- type=click.Path(),
43
- help="Nextflow config file path",
22
+ help="Execute the script using the cached results, useful to continue "
23
+ "executions that were stopped by an error",
44
24
  )
45
-
46
- OPTION_PARAMS_FILE = click.option(
47
- "--params-file",
48
- type=click.Path(),
49
- help="Nextflow workflow-specific parameter file path",
50
- )
51
-
52
- OPTION_USE_NEXTFLOW = click.option(
53
- "--use-nextflow",
54
- type=bool,
55
- is_flag=True,
56
- default=False,
57
- show_default=True,
58
- help="Execute workflow using nextflow",
59
- )
60
-
61
25
  OPTION_REVISION = click.option(
62
26
  "--revision",
63
27
  type=str,
64
28
  help="Revision of workflow to run (either a git branch, tag or commit SHA number)",
65
29
  )
66
- OPTION_COMPUTE_ENV = click.option(
67
- "--compute-env",
68
- type=str,
69
- help="Compute environment name. If not specified the primary compute environment will be used.",
70
- )
71
- OPTION_TOWER_RUN_ID = click.option(
72
- "--nf-tower-id",
73
- type=str,
74
- is_flag=False,
75
- default=None,
76
- help="NF-Tower ID of run to relaunch. If not provided the latest NF-Tower ID for a case will be used.",
77
- )
78
- OPTION_FROM_START = click.option(
79
- "--from-start",
80
- is_flag=True,
81
- default=False,
82
- show_default=True,
83
- help="Start workflow from start without resuming execution",
84
- )
85
- OPTION_STUB = click.option(
86
- "--stub-run",
87
- is_flag=True,
88
- default=False,
89
- show_default=True,
90
- help="Start a stub workflow",
91
- )
92
-
93
-
94
- @click.command("config-case")
95
- @ARGUMENT_CASE_ID
96
- @DRY_RUN
97
- @click.pass_obj
98
- def config_case(context: CGConfig, case_id: str, dry_run: bool) -> None:
99
- """Create config files required by a workflow for a case."""
100
- analysis_api: NfAnalysisAPI = context.meta_apis[MetaApis.ANALYSIS_API]
101
- try:
102
- analysis_api.config_case(case_id=case_id, dry_run=dry_run)
103
- except (CgError, ValidationError) as error:
104
- LOG.error(f"Could not create config files for {case_id}: {error}")
105
- raise click.Abort() from error
106
-
107
-
108
- @click.command("run")
109
- @ARGUMENT_CASE_ID
110
- @OPTION_WORKDIR
111
- @OPTION_FROM_START
112
- @OPTION_PROFILE
113
- @OPTION_CONFIG
114
- @OPTION_PARAMS_FILE
115
- @OPTION_REVISION
116
- @OPTION_COMPUTE_ENV
117
- @OPTION_USE_NEXTFLOW
118
- @OPTION_TOWER_RUN_ID
119
- @OPTION_STUB
120
- @DRY_RUN
121
- @click.pass_obj
122
- def run(
123
- context: CGConfig,
124
- case_id: str,
125
- work_dir: str,
126
- from_start: bool,
127
- profile: str,
128
- config: str,
129
- params_file: str,
130
- revision: str,
131
- compute_env: str,
132
- use_nextflow: bool,
133
- nf_tower_id: str | None,
134
- stub_run: bool,
135
- dry_run: bool,
136
- ) -> None:
137
- """Run analysis for a case."""
138
- analysis_api: NfAnalysisAPI = context.meta_apis[MetaApis.ANALYSIS_API]
139
- try:
140
- analysis_api.run_nextflow_analysis(
141
- case_id=case_id,
142
- dry_run=dry_run,
143
- work_dir=work_dir,
144
- from_start=from_start,
145
- profile=profile,
146
- config=config,
147
- params_file=params_file,
148
- revision=revision,
149
- compute_env=compute_env,
150
- use_nextflow=use_nextflow,
151
- nf_tower_id=nf_tower_id,
152
- stub_run=stub_run,
153
- )
154
- except Exception as error:
155
- LOG.error(f"Unspecified error occurred: {error}")
156
- raise click.Abort() from error
157
-
158
-
159
- @click.command("start")
160
- @ARGUMENT_CASE_ID
161
- @OPTION_WORKDIR
162
- @OPTION_PROFILE
163
- @OPTION_CONFIG
164
- @OPTION_PARAMS_FILE
165
- @OPTION_REVISION
166
- @OPTION_COMPUTE_ENV
167
- @OPTION_USE_NEXTFLOW
168
- @OPTION_STUB
169
- @DRY_RUN
170
- @click.pass_obj
171
- def start(
172
- context: CGConfig,
173
- case_id: str,
174
- work_dir: str,
175
- profile: str,
176
- config: str,
177
- params_file: str,
178
- revision: str,
179
- compute_env: str,
180
- use_nextflow: bool,
181
- stub_run: bool,
182
- dry_run: bool,
183
- ) -> None:
184
- """Start workflow for a case."""
185
- LOG.info(f"Starting analysis for {case_id}")
186
- analysis_api: NfAnalysisAPI = context.meta_apis[MetaApis.ANALYSIS_API]
187
- try:
188
- analysis_api.status_db.verify_case_exists(case_internal_id=case_id)
189
- case: Case = analysis_api.status_db.get_case_by_internal_id(case_id)
190
- if case.data_analysis != Workflow.NALLO:
191
- analysis_api.prepare_fastq_files(case_id=case_id, dry_run=dry_run)
192
- analysis_api.config_case(case_id=case_id, dry_run=dry_run)
193
- analysis_api.run_nextflow_analysis(
194
- case_id=case_id,
195
- dry_run=dry_run,
196
- work_dir=work_dir,
197
- from_start=True,
198
- profile=profile,
199
- config=config,
200
- params_file=params_file,
201
- revision=revision,
202
- compute_env=compute_env,
203
- use_nextflow=use_nextflow,
204
- stub_run=stub_run,
205
- )
206
- except Exception as error:
207
- LOG.error(f"Unexpected error occurred: {error}")
208
- raise click.Abort from error
209
-
210
-
211
- @click.command("start-available")
212
- @DRY_RUN
213
- @click.pass_context
214
- def start_available(context: click.Context, dry_run: bool = False) -> None:
215
- """Start workflow for all cases ready for analysis."""
216
- analysis_api: NfAnalysisAPI = context.obj.meta_apis[MetaApis.ANALYSIS_API]
217
-
218
- cases: list[Case] = analysis_api.get_cases_to_analyze()
219
- LOG.info(f"Starting {len(cases)} available {analysis_api.workflow} cases")
220
-
221
- exit_code: int = EXIT_SUCCESS
222
- for case in cases:
223
- try:
224
- context.invoke(start, case_id=case.internal_id, dry_run=dry_run)
225
- except AnalysisNotReadyError as error:
226
- LOG.error(error)
227
- except Exception as error:
228
- LOG.error(error)
229
- exit_code = EXIT_FAIL
230
- if exit_code:
231
- raise click.Abort
232
30
 
233
31
 
234
32
  @click.command("metrics-deliver")