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.
- cg/__init__.py +1 -1
- cg/apps/housekeeper/hk.py +18 -1
- cg/apps/tb/api.py +42 -5
- cg/cli/transfer.py +13 -2
- cg/cli/upload/mutacc.py +16 -3
- cg/cli/upload/scout.py +2 -2
- cg/cli/upload/utils.py +10 -1
- cg/cli/workflow/balsamic/base.py +86 -172
- cg/cli/workflow/balsamic/options.py +3 -48
- cg/cli/workflow/balsamic/umi.py +210 -15
- cg/cli/workflow/microsalt/base.py +4 -2
- cg/cli/workflow/mip_dna/base.py +1 -1
- cg/cli/workflow/nallo/base.py +73 -23
- cg/cli/workflow/nf_analysis.py +5 -207
- cg/cli/workflow/raredisease/base.py +41 -54
- cg/cli/workflow/rnafusion/base.py +38 -8
- cg/cli/workflow/taxprofiler/base.py +31 -18
- cg/cli/workflow/tomte/base.py +83 -10
- cg/constants/constants.py +25 -30
- cg/constants/devices.py +6 -1
- cg/constants/gene_panel.py +3 -1
- cg/constants/housekeeper_tags.py +28 -28
- cg/constants/lims.py +4 -0
- cg/constants/nf_analysis.py +0 -1
- cg/constants/observations.py +21 -5
- cg/constants/orderforms.py +3 -3
- cg/constants/pacbio.py +1 -0
- cg/constants/priority.py +1 -1
- cg/constants/report.py +1 -0
- cg/constants/scout.py +12 -9
- cg/constants/sequencing.py +2 -2
- cg/constants/tb.py +5 -5
- cg/exc.py +27 -5
- cg/meta/compress/compress.py +7 -2
- cg/meta/delivery_report/balsamic.py +3 -1
- cg/meta/delivery_report/delivery_report_api.py +4 -3
- cg/meta/delivery_report/nallo.py +11 -11
- cg/meta/delivery_report/raredisease.py +7 -3
- cg/meta/delivery_report/templates/macros/data_analysis/qc_metrics/balsamic_qc_metrics.html +1 -0
- cg/meta/delivery_report/templates/macros/ticket_system.html +1 -1
- cg/meta/observations/balsamic_observations_api.py +110 -14
- cg/meta/observations/mip_dna_observations_api.py +1 -1
- cg/meta/observations/nallo_observations_api.py +1 -1
- cg/meta/observations/observations_api.py +23 -32
- cg/meta/observations/raredisease_observations_api.py +1 -1
- cg/meta/tar/tar.py +5 -2
- cg/meta/transfer/lims.py +32 -3
- cg/meta/upload/balsamic/balsamic.py +1 -8
- cg/meta/upload/coverage.py +5 -5
- cg/meta/upload/raredisease/raredisease.py +3 -0
- cg/meta/upload/scout/hk_tags.py +1 -0
- cg/meta/upload/scout/nallo_config_builder.py +31 -7
- cg/meta/workflow/balsamic.py +70 -36
- cg/meta/workflow/fastq.py +8 -0
- cg/meta/workflow/microsalt/quality_controller/models.py +0 -2
- cg/meta/workflow/microsalt/quality_controller/quality_controller.py +8 -16
- cg/meta/workflow/microsalt/quality_controller/result_logger.py +3 -6
- cg/meta/workflow/microsalt/quality_controller/utils.py +2 -45
- cg/meta/workflow/nallo.py +21 -99
- cg/meta/workflow/nf_analysis.py +12 -263
- cg/meta/workflow/raredisease.py +3 -112
- cg/meta/workflow/rnafusion.py +2 -34
- cg/meta/workflow/taxprofiler.py +2 -38
- cg/meta/workflow/tomte.py +2 -42
- cg/models/balsamic/config.py +0 -24
- cg/models/balsamic/metrics.py +5 -3
- cg/models/cg_config.py +39 -16
- cg/models/deliverables/metric_deliverables.py +1 -1
- cg/models/delivery_report/metadata.py +2 -1
- cg/models/nallo/nallo.py +14 -64
- cg/models/nf_analysis.py +1 -41
- cg/models/raredisease/raredisease.py +1 -63
- cg/models/rnafusion/rnafusion.py +0 -26
- cg/models/scout/scout_load_config.py +5 -2
- cg/models/taxprofiler/taxprofiler.py +0 -42
- cg/models/tomte/tomte.py +0 -69
- cg/resources/nallo_bundle_filenames.yaml +292 -22
- cg/resources/raredisease_bundle_filenames.yaml +11 -1
- cg/resources/taxprofiler_bundle_filenames.yaml +20 -0
- cg/server/admin.py +106 -25
- cg/server/app.py +15 -4
- cg/server/endpoints/sequencing_run/dtos.py +21 -3
- cg/server/endpoints/sequencing_run/pacbio_sequencing_run.py +29 -10
- cg/server/endpoints/sequencing_run/pacbio_smrt_cell_metrics.py +20 -0
- cg/services/analysis_starter/{service.py → analysis_starter.py} +11 -9
- cg/services/analysis_starter/configurator/abstract_model.py +8 -0
- cg/services/analysis_starter/configurator/configurator.py +1 -1
- cg/services/analysis_starter/configurator/extensions/nallo.py +27 -0
- cg/services/analysis_starter/configurator/extensions/{abstract.py → pipeline_extension.py} +1 -1
- cg/services/analysis_starter/configurator/extensions/raredisease.py +3 -1
- cg/services/analysis_starter/configurator/extensions/tomte_extension.py +28 -0
- cg/services/analysis_starter/configurator/file_creators/balsamic_config.py +240 -0
- cg/services/analysis_starter/configurator/file_creators/gene_panel.py +10 -5
- cg/services/analysis_starter/configurator/file_creators/nextflow/params_file/abstract.py +2 -1
- cg/services/analysis_starter/configurator/file_creators/nextflow/params_file/models.py +40 -1
- cg/services/analysis_starter/configurator/file_creators/nextflow/params_file/nallo.py +37 -0
- cg/services/analysis_starter/configurator/file_creators/nextflow/params_file/raredisease.py +8 -5
- cg/services/analysis_starter/configurator/file_creators/nextflow/params_file/tomte_params_file_creator.py +64 -0
- cg/services/analysis_starter/configurator/file_creators/nextflow/sample_sheet/creator.py +1 -1
- cg/services/analysis_starter/configurator/file_creators/nextflow/sample_sheet/nallo_sample_sheet_creator.py +65 -0
- cg/services/analysis_starter/configurator/file_creators/nextflow/sample_sheet/protocol.py +12 -0
- cg/services/analysis_starter/configurator/file_creators/nextflow/sample_sheet/{raredisease.py → raredisease_sample_sheet_creator.py} +2 -2
- cg/services/analysis_starter/configurator/file_creators/nextflow/sample_sheet/{rnafusion.py → rnafusion_sample_sheet_creator.py} +2 -2
- cg/services/analysis_starter/configurator/file_creators/nextflow/sample_sheet/{taxprofiler.py → taxprofiler_sample_sheet_creator.py} +2 -2
- cg/services/analysis_starter/configurator/file_creators/nextflow/sample_sheet/tomte_sample_sheet_creator.py +36 -0
- cg/services/analysis_starter/configurator/implementations/balsamic.py +68 -0
- cg/services/analysis_starter/configurator/implementations/nextflow.py +22 -5
- cg/services/analysis_starter/configurator/models/balsamic.py +152 -0
- cg/services/analysis_starter/configurator/models/mip_dna.py +6 -8
- cg/services/analysis_starter/configurator/models/nextflow.py +9 -0
- cg/services/analysis_starter/constants.py +2 -0
- cg/services/analysis_starter/factories/configurator_factory.py +131 -51
- cg/services/analysis_starter/factories/starter_factory.py +36 -7
- cg/services/analysis_starter/input_fetcher/implementations/bam_fetcher.py +57 -0
- cg/services/analysis_starter/input_fetcher/implementations/fastq_fetcher.py +3 -3
- cg/services/analysis_starter/submitters/seqera_platform/{client.py → seqera_platform_client.py} +19 -3
- cg/services/analysis_starter/submitters/seqera_platform/seqera_platform_submitter.py +73 -0
- cg/services/analysis_starter/submitters/submitter.py +1 -1
- cg/services/analysis_starter/submitters/subprocess/submitter.py +2 -1
- cg/services/analysis_starter/tracker/implementations/balsamic.py +22 -0
- cg/services/analysis_starter/tracker/implementations/microsalt.py +4 -4
- cg/services/analysis_starter/tracker/implementations/mip_dna.py +4 -1
- cg/services/analysis_starter/tracker/implementations/{nextflow.py → nextflow_tracker.py} +6 -4
- cg/services/analysis_starter/tracker/tracker.py +19 -15
- cg/services/deliver_files/factory.py +1 -1
- cg/services/delivery_message/messages/__init__.py +24 -14
- cg/services/delivery_message/messages/{microsalt_mwr_message.py → microsalt_message.py} +1 -1
- cg/services/delivery_message/utils.py +4 -40
- cg/services/illumina/backup/backup_service.py +29 -7
- cg/services/orders/validation/constants.py +3 -0
- cg/services/orders/validation/index_sequences.py +558 -0
- cg/services/orders/validation/order_types/microsalt/models/sample.py +2 -3
- cg/services/run_devices/pacbio/data_storage_service/pacbio_store_service.py +39 -18
- cg/services/run_devices/pacbio/data_transfer_service/data_transfer_service.py +8 -2
- cg/services/run_devices/pacbio/data_transfer_service/dto.py +9 -3
- cg/services/run_devices/pacbio/data_transfer_service/utils.py +14 -7
- cg/services/run_devices/pacbio/metrics_parser/models.py +1 -0
- cg/services/run_devices/pacbio/sequencing_runs_service.py +35 -7
- cg/services/sequencing_qc_service/quality_checks/checks.py +18 -16
- cg/services/sequencing_qc_service/quality_checks/utils.py +82 -18
- cg/services/sequencing_qc_service/sequencing_qc_service.py +12 -10
- cg/store/crud/create.py +73 -42
- cg/store/crud/read.py +73 -7
- cg/store/crud/update.py +14 -3
- cg/store/models.py +98 -35
- cg/store/store.py +8 -1
- {cg-76.0.0.dist-info → cg-83.14.0.dist-info}/METADATA +1 -1
- {cg-76.0.0.dist-info → cg-83.14.0.dist-info}/RECORD +150 -138
- cg/services/analysis_starter/submitters/seqera_platform/submitter.py +0 -39
- cg/services/delivery_message/messages/microsalt_mwx_message.py +0 -18
- {cg-76.0.0.dist-info → cg-83.14.0.dist-info}/WHEEL +0 -0
- {cg-76.0.0.dist-info → cg-83.14.0.dist-info}/entry_points.txt +0 -0
cg/__init__.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
__title__ = "cg"
|
|
2
|
-
__version__ = "
|
|
2
|
+
__version__ = "83.14.0"
|
cg/apps/housekeeper/hk.py
CHANGED
|
@@ -120,7 +120,7 @@ class HousekeeperAPI:
|
|
|
120
120
|
*,
|
|
121
121
|
bundle: str = None,
|
|
122
122
|
tags: set[str] = None,
|
|
123
|
-
version: int = None,
|
|
123
|
+
version: int | None = None,
|
|
124
124
|
path: str = None,
|
|
125
125
|
) -> Query:
|
|
126
126
|
"""Fetch files."""
|
|
@@ -181,6 +181,23 @@ class HousekeeperAPI:
|
|
|
181
181
|
files: Query = self._store.get_files(bundle_name=bundle, tag_names=tags, version_id=version)
|
|
182
182
|
return files.order_by(File.id.desc()).first()
|
|
183
183
|
|
|
184
|
+
def get_latest_file_strict(
|
|
185
|
+
self, bundle: str, tags: list | None = None, version: int | None = None
|
|
186
|
+
) -> File:
|
|
187
|
+
"""
|
|
188
|
+
Return latest file from Housekeeper, filtered by bundle and/or tags and/or version.
|
|
189
|
+
Raises:
|
|
190
|
+
HousekeeperFileMissingError if no file is found.
|
|
191
|
+
"""
|
|
192
|
+
files: Query = self._store.get_files(bundle_name=bundle, tag_names=tags, version_id=version)
|
|
193
|
+
latest_file: File | None = files.order_by(File.id.desc()).first()
|
|
194
|
+
if not latest_file:
|
|
195
|
+
raise HousekeeperFileMissingError(
|
|
196
|
+
f"No file matching bundle {bundle}, version {version} with tags {tags}"
|
|
197
|
+
)
|
|
198
|
+
else:
|
|
199
|
+
return latest_file
|
|
200
|
+
|
|
184
201
|
def check_bundle_files(
|
|
185
202
|
self,
|
|
186
203
|
bundle_name: str,
|
cg/apps/tb/api.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Trailblazer API for cg."""
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
-
from datetime import datetime
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
5
|
from typing import Any
|
|
6
6
|
|
|
7
7
|
from google.auth.transport.requests import Request
|
|
@@ -34,15 +34,52 @@ class TrailblazerAPI:
|
|
|
34
34
|
self.service_account = config["trailblazer"]["service_account"]
|
|
35
35
|
self.service_account_auth_file = config["trailblazer"]["service_account_auth_file"]
|
|
36
36
|
self.host = config["trailblazer"]["host"]
|
|
37
|
+
self._credentials: IDTokenCredentials | None = None
|
|
37
38
|
|
|
38
|
-
|
|
39
|
-
|
|
39
|
+
def _are_credentials_expired(self) -> bool:
|
|
40
|
+
"""Return True when there are no cached credentials or the token has expired."""
|
|
41
|
+
if not self._credentials:
|
|
42
|
+
return True
|
|
43
|
+
|
|
44
|
+
expiry: datetime | None = getattr(self._credentials, "expiry", None)
|
|
45
|
+
if expiry is None:
|
|
46
|
+
return True
|
|
47
|
+
|
|
48
|
+
if expiry.tzinfo is None:
|
|
49
|
+
expiry = expiry.replace(tzinfo=timezone.utc)
|
|
50
|
+
|
|
51
|
+
return datetime.now(tz=timezone.utc) >= expiry
|
|
52
|
+
|
|
53
|
+
def _refresh_credentials(self) -> IDTokenCredentials:
|
|
54
|
+
"""Refresh the Google OAuth token and cache the credential object."""
|
|
55
|
+
LOG.debug("Refreshing Google OAuth token for Trailblazer authentication")
|
|
40
56
|
credentials: IDTokenCredentials = IDTokenCredentials.from_service_account_file(
|
|
41
57
|
filename=self.service_account_auth_file,
|
|
42
58
|
target_audience="trailblazer",
|
|
43
59
|
)
|
|
44
60
|
credentials.refresh(Request())
|
|
45
|
-
|
|
61
|
+
|
|
62
|
+
self._credentials = credentials
|
|
63
|
+
expiry: datetime | None = getattr(self._credentials, "expiry", None)
|
|
64
|
+
if expiry:
|
|
65
|
+
if expiry.tzinfo is None:
|
|
66
|
+
expiry = expiry.replace(tzinfo=timezone.utc)
|
|
67
|
+
LOG.debug(
|
|
68
|
+
"Token cached until %s (%s local)",
|
|
69
|
+
expiry,
|
|
70
|
+
expiry.astimezone().isoformat(),
|
|
71
|
+
)
|
|
72
|
+
else:
|
|
73
|
+
LOG.debug("Token refreshed without expiry timestamp")
|
|
74
|
+
return self._credentials
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def auth_header(self) -> dict:
|
|
78
|
+
"""Get authorization header with cached token to prevent Google rate limiting."""
|
|
79
|
+
if not self._credentials or self._are_credentials_expired():
|
|
80
|
+
self._refresh_credentials()
|
|
81
|
+
|
|
82
|
+
return {"Authorization": f"Bearer {self._credentials.token}"}
|
|
46
83
|
|
|
47
84
|
def query_trailblazer(
|
|
48
85
|
self, command: str, request_body: dict, method: str = APIMethods.POST
|
|
@@ -97,7 +134,7 @@ class TrailblazerAPI:
|
|
|
97
134
|
self,
|
|
98
135
|
case_id: str,
|
|
99
136
|
analysis_type: str,
|
|
100
|
-
config_path: str,
|
|
137
|
+
config_path: str | None,
|
|
101
138
|
out_dir: str,
|
|
102
139
|
priority: TrailblazerPriority,
|
|
103
140
|
workflow: Workflow,
|
cg/cli/transfer.py
CHANGED
|
@@ -23,6 +23,12 @@ def transfer_group(context: CGConfig):
|
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
@transfer_group.command("lims")
|
|
26
|
+
@click.option(
|
|
27
|
+
"--max-order-age",
|
|
28
|
+
type=click.IntRange(min=1),
|
|
29
|
+
default=None,
|
|
30
|
+
help="Exclude samples with order dates older than N years. Must be greater than or equal to 1.",
|
|
31
|
+
)
|
|
26
32
|
@click.option(
|
|
27
33
|
"-s", "--status", type=click.Choice(["received", "prepared", "delivered"]), default="received"
|
|
28
34
|
)
|
|
@@ -31,11 +37,16 @@ def transfer_group(context: CGConfig):
|
|
|
31
37
|
)
|
|
32
38
|
@click.option("--sample-id", help="Lims Submitted Sample id. use together with status.")
|
|
33
39
|
@click.pass_obj
|
|
34
|
-
def check_samples_in_lims(
|
|
40
|
+
def check_samples_in_lims(
|
|
41
|
+
context: CGConfig, max_order_age: int | None, status: str, include: str, sample_id: str
|
|
42
|
+
):
|
|
35
43
|
"""Check if samples have been updated in LIMS."""
|
|
36
44
|
transfer_api: TransferLims = context.meta_apis["transfer_lims_api"]
|
|
37
45
|
transfer_api.transfer_samples(
|
|
38
|
-
|
|
46
|
+
include=include,
|
|
47
|
+
max_order_age=max_order_age,
|
|
48
|
+
sample_id=sample_id,
|
|
49
|
+
status_type=SampleState[status.upper()],
|
|
39
50
|
)
|
|
40
51
|
|
|
41
52
|
|
cg/cli/upload/mutacc.py
CHANGED
|
@@ -7,7 +7,7 @@ import rich_click as click
|
|
|
7
7
|
from cg.apps.mutacc_auto import MutaccAutoAPI
|
|
8
8
|
from cg.apps.scout.scout_export import ScoutExportCase
|
|
9
9
|
from cg.apps.scout.scoutapi import ScoutAPI
|
|
10
|
-
from cg.cli.upload.utils import
|
|
10
|
+
from cg.cli.upload.utils import get_scout_api_by_genome_build
|
|
11
11
|
from cg.constants.cli_options import DRY_RUN
|
|
12
12
|
from cg.meta.upload.mutacc import UploadToMutaccAPI
|
|
13
13
|
from cg.models.cg_config import CGConfig
|
|
@@ -19,17 +19,30 @@ LOG = logging.getLogger(__name__)
|
|
|
19
19
|
@click.option("-c", "--case-id", help="internal case id, leave empty to process all")
|
|
20
20
|
@click.option("-d", "--days-ago", type=int, default=1, help="days since solved")
|
|
21
21
|
@click.option("-C", "--customers", type=str, multiple=True, help="Filter on customers")
|
|
22
|
+
@click.option(
|
|
23
|
+
"--scout-instance",
|
|
24
|
+
type=click.Choice(["hg19", "hg38"]),
|
|
25
|
+
help="Which scout instance to fetch cases from",
|
|
26
|
+
required=True,
|
|
27
|
+
)
|
|
22
28
|
@DRY_RUN
|
|
23
29
|
@click.pass_obj
|
|
24
30
|
def process_solved(
|
|
25
|
-
context: CGConfig,
|
|
31
|
+
context: CGConfig,
|
|
32
|
+
scout_instance: str,
|
|
33
|
+
case_id: str | None,
|
|
34
|
+
days_ago: int,
|
|
35
|
+
customers: tuple[str],
|
|
36
|
+
dry_run: bool,
|
|
26
37
|
):
|
|
27
38
|
"""Process cases with mutacc that has been marked as solved in scout.
|
|
28
39
|
This prepares them to be uploaded to the mutacc database"""
|
|
29
40
|
|
|
30
41
|
LOG.info("----------------- PROCESS-SOLVED ----------------")
|
|
31
42
|
|
|
32
|
-
scout_api: ScoutAPI =
|
|
43
|
+
scout_api: ScoutAPI = get_scout_api_by_genome_build(
|
|
44
|
+
cg_config=context, genome_build=scout_instance
|
|
45
|
+
)
|
|
33
46
|
mutacc_auto_api: MutaccAutoAPI = context.mutacc_auto_api
|
|
34
47
|
mutacc_upload_api = UploadToMutaccAPI(scout_api=scout_api, mutacc_auto_api=mutacc_auto_api)
|
|
35
48
|
|
cg/cli/upload/scout.py
CHANGED
|
@@ -8,7 +8,7 @@ from housekeeper.store.models import File, Version
|
|
|
8
8
|
|
|
9
9
|
from cg.apps.housekeeper.hk import HousekeeperAPI
|
|
10
10
|
from cg.apps.scout.scoutapi import ScoutAPI
|
|
11
|
-
from cg.cli.upload.utils import
|
|
11
|
+
from cg.cli.upload.utils import get_scout_api_by_case, suggest_cases_to_upload
|
|
12
12
|
from cg.constants import Workflow
|
|
13
13
|
from cg.constants.cli_options import DRY_RUN
|
|
14
14
|
from cg.constants.constants import FileFormat
|
|
@@ -145,7 +145,7 @@ def upload_case_to_scout(context: CGConfig, re_upload: bool, dry_run: bool, case
|
|
|
145
145
|
LOG.info("----------------- UPLOAD -----------------------")
|
|
146
146
|
|
|
147
147
|
housekeeper_api: HousekeeperAPI = context.housekeeper_api
|
|
148
|
-
scout_api: ScoutAPI =
|
|
148
|
+
scout_api: ScoutAPI = get_scout_api_by_case(cg_config=context, case_id=case_id)
|
|
149
149
|
|
|
150
150
|
tag_name: str = UploadScoutAPI.get_load_config_tag()
|
|
151
151
|
version: Version = housekeeper_api.last_version(bundle=case_id)
|
cg/cli/upload/utils.py
CHANGED
|
@@ -24,6 +24,15 @@ def suggest_cases_to_upload(status_db: Store, workflow: Workflow | None = None)
|
|
|
24
24
|
click.echo(case_obj)
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
def
|
|
27
|
+
def get_scout_api_by_case(cg_config: CGConfig, case_id: str) -> ScoutAPI:
|
|
28
28
|
workflow = cg_config.status_db.get_case_by_internal_id(case_id).data_analysis
|
|
29
29
|
return cg_config.scout_api_38 if workflow == Workflow.NALLO else cg_config.scout_api_37
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def get_scout_api_by_genome_build(cg_config: CGConfig, genome_build: str) -> ScoutAPI:
|
|
33
|
+
"""Return the appropriate ScoutAPI based on the genome build."""
|
|
34
|
+
if genome_build == "hg38":
|
|
35
|
+
return cg_config.scout_api_38
|
|
36
|
+
if genome_build == "hg19":
|
|
37
|
+
return cg_config.scout_api_37
|
|
38
|
+
raise ValueError(f"Unsupported genome build: {genome_build}")
|
cg/cli/workflow/balsamic/base.py
CHANGED
|
@@ -1,31 +1,26 @@
|
|
|
1
1
|
"""CLI support to create config and/or start BALSAMIC."""
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
+
from typing import cast
|
|
4
5
|
|
|
5
6
|
import rich_click as click
|
|
6
7
|
from pydantic.v1 import ValidationError
|
|
7
8
|
|
|
8
9
|
from cg.apps.housekeeper.hk import HousekeeperAPI
|
|
9
10
|
from cg.cli.utils import CLICK_CONTEXT_SETTINGS
|
|
10
|
-
from cg.cli.workflow.balsamic.options import
|
|
11
|
-
|
|
12
|
-
OPTION_CLUSTER_CONFIG,
|
|
13
|
-
OPTION_GENDER,
|
|
14
|
-
OPTION_GENOME_VERSION,
|
|
15
|
-
OPTION_OBSERVATIONS,
|
|
16
|
-
OPTION_PANEL_BED,
|
|
17
|
-
OPTION_PON_CNN,
|
|
18
|
-
OPTION_QOS,
|
|
19
|
-
)
|
|
20
|
-
from cg.cli.workflow.commands import ARGUMENT_CASE_ID, link, resolve_compression
|
|
11
|
+
from cg.cli.workflow.balsamic.options import OPTION_PANEL_BED, OPTION_WORKFLOW_PROFILE
|
|
12
|
+
from cg.cli.workflow.commands import ARGUMENT_CASE_ID, resolve_compression
|
|
21
13
|
from cg.cli.workflow.utils import validate_force_store_option
|
|
22
|
-
from cg.constants import
|
|
23
|
-
from cg.constants.cli_options import COMMENT, DRY_RUN, FORCE
|
|
24
|
-
from cg.exc import
|
|
14
|
+
from cg.constants import Workflow
|
|
15
|
+
from cg.constants.cli_options import COMMENT, DRY_RUN, FORCE
|
|
16
|
+
from cg.exc import CgError
|
|
25
17
|
from cg.meta.workflow.analysis import AnalysisAPI
|
|
26
18
|
from cg.meta.workflow.balsamic import BalsamicAnalysisAPI
|
|
27
19
|
from cg.models.cg_config import CGConfig
|
|
28
|
-
from cg.
|
|
20
|
+
from cg.services.analysis_starter.analysis_starter import AnalysisStarter
|
|
21
|
+
from cg.services.analysis_starter.configurator.implementations.balsamic import BalsamicConfigurator
|
|
22
|
+
from cg.services.analysis_starter.factories.configurator_factory import ConfiguratorFactory
|
|
23
|
+
from cg.services.analysis_starter.factories.starter_factory import AnalysisStarterFactory
|
|
29
24
|
from cg.store.store import Store
|
|
30
25
|
|
|
31
26
|
LOG = logging.getLogger(__name__)
|
|
@@ -42,85 +37,6 @@ def balsamic(context: click.Context):
|
|
|
42
37
|
|
|
43
38
|
|
|
44
39
|
balsamic.add_command(resolve_compression)
|
|
45
|
-
balsamic.add_command(link)
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
@balsamic.command("config-case")
|
|
49
|
-
@ARGUMENT_CASE_ID
|
|
50
|
-
@OPTION_GENDER
|
|
51
|
-
@OPTION_GENOME_VERSION
|
|
52
|
-
@OPTION_PANEL_BED
|
|
53
|
-
@OPTION_PON_CNN
|
|
54
|
-
@OPTION_OBSERVATIONS
|
|
55
|
-
@OPTION_CACHE_VERSION
|
|
56
|
-
@DRY_RUN
|
|
57
|
-
@click.pass_obj
|
|
58
|
-
def config_case(
|
|
59
|
-
context: CGConfig,
|
|
60
|
-
case_id: str,
|
|
61
|
-
gender: str,
|
|
62
|
-
genome_version: str,
|
|
63
|
-
panel_bed: str,
|
|
64
|
-
pon_cnn: click.Path,
|
|
65
|
-
observations: list[click.Path],
|
|
66
|
-
cache_version: str,
|
|
67
|
-
dry_run: bool,
|
|
68
|
-
):
|
|
69
|
-
"""Create config file for BALSAMIC analysis for a given CASE_ID."""
|
|
70
|
-
|
|
71
|
-
analysis_api: AnalysisAPI = context.meta_apis["analysis_api"]
|
|
72
|
-
try:
|
|
73
|
-
LOG.info(f"Creating config file for {case_id}.")
|
|
74
|
-
analysis_api.status_db.verify_case_exists(case_internal_id=case_id)
|
|
75
|
-
analysis_api.config_case(
|
|
76
|
-
case_id=case_id,
|
|
77
|
-
gender=gender,
|
|
78
|
-
genome_version=genome_version,
|
|
79
|
-
panel_bed=panel_bed,
|
|
80
|
-
pon_cnn=pon_cnn,
|
|
81
|
-
observations=observations,
|
|
82
|
-
cache_version=cache_version,
|
|
83
|
-
dry_run=dry_run,
|
|
84
|
-
)
|
|
85
|
-
except CgError as error:
|
|
86
|
-
LOG.error(f"Could not create config: {error}")
|
|
87
|
-
raise click.Abort()
|
|
88
|
-
except Exception as error:
|
|
89
|
-
LOG.error(f"Could not create config: {error}")
|
|
90
|
-
raise click.Abort()
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
@balsamic.command("run")
|
|
94
|
-
@ARGUMENT_CASE_ID
|
|
95
|
-
@OPTION_CLUSTER_CONFIG
|
|
96
|
-
@DRY_RUN
|
|
97
|
-
@OPTION_QOS
|
|
98
|
-
@click.pass_obj
|
|
99
|
-
def run(
|
|
100
|
-
context: CGConfig,
|
|
101
|
-
case_id: str,
|
|
102
|
-
cluster_config: click.Path,
|
|
103
|
-
slurm_quality_of_service: str,
|
|
104
|
-
dry_run: bool,
|
|
105
|
-
):
|
|
106
|
-
"""Run balsamic analysis for given CASE ID"""
|
|
107
|
-
analysis_api: AnalysisAPI = context.meta_apis["analysis_api"]
|
|
108
|
-
try:
|
|
109
|
-
analysis_api.status_db.verify_case_exists(case_internal_id=case_id)
|
|
110
|
-
analysis_api.verify_case_config_file_exists(case_id=case_id, dry_run=dry_run)
|
|
111
|
-
analysis_api.check_analysis_ongoing(case_id)
|
|
112
|
-
analysis_api.run_analysis(
|
|
113
|
-
case_id=case_id,
|
|
114
|
-
cluster_config=cluster_config,
|
|
115
|
-
slurm_quality_of_service=slurm_quality_of_service,
|
|
116
|
-
dry_run=dry_run,
|
|
117
|
-
)
|
|
118
|
-
if dry_run:
|
|
119
|
-
return
|
|
120
|
-
analysis_api.on_analysis_started(case_id)
|
|
121
|
-
except Exception as error:
|
|
122
|
-
LOG.error(f"Could not run analysis: {error}")
|
|
123
|
-
raise click.Abort()
|
|
124
40
|
|
|
125
41
|
|
|
126
42
|
@balsamic.command("report-deliver")
|
|
@@ -185,84 +101,6 @@ def store_housekeeper(
|
|
|
185
101
|
raise click.Abort()
|
|
186
102
|
|
|
187
103
|
|
|
188
|
-
@balsamic.command("start")
|
|
189
|
-
@ARGUMENT_CASE_ID
|
|
190
|
-
@OPTION_GENDER
|
|
191
|
-
@OPTION_GENOME_VERSION
|
|
192
|
-
@OPTION_QOS
|
|
193
|
-
@DRY_RUN
|
|
194
|
-
@OPTION_PANEL_BED
|
|
195
|
-
@OPTION_PON_CNN
|
|
196
|
-
@OPTION_CACHE_VERSION
|
|
197
|
-
@OPTION_OBSERVATIONS
|
|
198
|
-
@OPTION_CLUSTER_CONFIG
|
|
199
|
-
@click.pass_context
|
|
200
|
-
def start(
|
|
201
|
-
context: click.Context,
|
|
202
|
-
case_id: str,
|
|
203
|
-
gender: str,
|
|
204
|
-
genome_version: str,
|
|
205
|
-
cache_version: str,
|
|
206
|
-
panel_bed: str,
|
|
207
|
-
pon_cnn: str,
|
|
208
|
-
observations: list[click.Path],
|
|
209
|
-
slurm_quality_of_service: str,
|
|
210
|
-
cluster_config: click.Path,
|
|
211
|
-
dry_run: bool,
|
|
212
|
-
):
|
|
213
|
-
"""Start full workflow for case ID."""
|
|
214
|
-
analysis_api: BalsamicAnalysisAPI = context.obj.meta_apis["analysis_api"]
|
|
215
|
-
analysis_api.prepare_fastq_files(case_id=case_id, dry_run=dry_run)
|
|
216
|
-
LOG.info(f"Starting analysis for {case_id}")
|
|
217
|
-
context.invoke(link, case_id=case_id, dry_run=dry_run)
|
|
218
|
-
context.invoke(
|
|
219
|
-
config_case,
|
|
220
|
-
case_id=case_id,
|
|
221
|
-
gender=gender,
|
|
222
|
-
genome_version=genome_version,
|
|
223
|
-
cache_version=cache_version,
|
|
224
|
-
panel_bed=panel_bed,
|
|
225
|
-
pon_cnn=pon_cnn,
|
|
226
|
-
observations=observations,
|
|
227
|
-
dry_run=dry_run,
|
|
228
|
-
)
|
|
229
|
-
context.invoke(
|
|
230
|
-
run,
|
|
231
|
-
case_id=case_id,
|
|
232
|
-
cluster_config=cluster_config,
|
|
233
|
-
slurm_quality_of_service=slurm_quality_of_service,
|
|
234
|
-
dry_run=dry_run,
|
|
235
|
-
)
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
@balsamic.command("start-available")
|
|
239
|
-
@DRY_RUN
|
|
240
|
-
@LIMIT
|
|
241
|
-
@click.pass_context
|
|
242
|
-
def start_available(context: click.Context, dry_run: bool = False, limit: int | None = None):
|
|
243
|
-
"""Start full workflow for all cases ready for analysis"""
|
|
244
|
-
|
|
245
|
-
analysis_api: AnalysisAPI = context.obj.meta_apis["analysis_api"]
|
|
246
|
-
|
|
247
|
-
cases: list[Case] = analysis_api.get_cases_to_analyze(limit=limit)
|
|
248
|
-
LOG.info(f"Starting {len(cases)} available Balsamic cases")
|
|
249
|
-
|
|
250
|
-
exit_code: int = EXIT_SUCCESS
|
|
251
|
-
for case in cases:
|
|
252
|
-
try:
|
|
253
|
-
context.invoke(start, case_id=case.internal_id, dry_run=dry_run)
|
|
254
|
-
except AnalysisNotReadyError as error:
|
|
255
|
-
LOG.error(error)
|
|
256
|
-
except CgError as error:
|
|
257
|
-
LOG.error(error)
|
|
258
|
-
exit_code = EXIT_FAIL
|
|
259
|
-
except Exception as error:
|
|
260
|
-
LOG.error(f"Unspecified error occurred: {error}")
|
|
261
|
-
exit_code = EXIT_FAIL
|
|
262
|
-
if exit_code:
|
|
263
|
-
raise click.Abort
|
|
264
|
-
|
|
265
|
-
|
|
266
104
|
@balsamic.command("store")
|
|
267
105
|
@ARGUMENT_CASE_ID
|
|
268
106
|
@COMMENT
|
|
@@ -295,3 +133,79 @@ def store_available(context: click.Context, dry_run: bool) -> None:
|
|
|
295
133
|
was_successful = False
|
|
296
134
|
if not was_successful:
|
|
297
135
|
raise click.Abort()
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
@balsamic.command("config-case")
|
|
139
|
+
@OPTION_PANEL_BED
|
|
140
|
+
@ARGUMENT_CASE_ID
|
|
141
|
+
@click.pass_obj
|
|
142
|
+
def config_case(cg_config: CGConfig, case_id: str, panel_bed: str | None):
|
|
143
|
+
"""Configure a Balsamic case so that it is ready to be run.
|
|
144
|
+
|
|
145
|
+
\b
|
|
146
|
+
Creates the case config file:
|
|
147
|
+
- CASE_ID.json
|
|
148
|
+
"""
|
|
149
|
+
factory = ConfiguratorFactory(cg_config)
|
|
150
|
+
configurator = cast(BalsamicConfigurator, factory.get_configurator(Workflow.BALSAMIC))
|
|
151
|
+
configurator.configure(case_id=case_id, panel_bed=panel_bed)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
@balsamic.command("run")
|
|
155
|
+
@OPTION_WORKFLOW_PROFILE
|
|
156
|
+
@ARGUMENT_CASE_ID
|
|
157
|
+
@click.pass_obj
|
|
158
|
+
def run(cg_config: CGConfig, case_id: str, workflow_profile: click.Path | None):
|
|
159
|
+
"""
|
|
160
|
+
Run a preconfigured Balsamic case.
|
|
161
|
+
|
|
162
|
+
\b
|
|
163
|
+
Assumes that case config file exist in the case run directory:
|
|
164
|
+
- CASE_ID.json
|
|
165
|
+
"""
|
|
166
|
+
factory = AnalysisStarterFactory(cg_config)
|
|
167
|
+
analysis_starter: AnalysisStarter = factory.get_analysis_starter_for_workflow(Workflow.BALSAMIC)
|
|
168
|
+
analysis_starter.run(case_id=case_id, workflow_profile=workflow_profile)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
@balsamic.command("start")
|
|
172
|
+
@OPTION_PANEL_BED
|
|
173
|
+
@OPTION_WORKFLOW_PROFILE
|
|
174
|
+
@ARGUMENT_CASE_ID
|
|
175
|
+
@click.pass_obj
|
|
176
|
+
def start(
|
|
177
|
+
cg_config: CGConfig,
|
|
178
|
+
case_id: str,
|
|
179
|
+
panel_bed: str | None,
|
|
180
|
+
workflow_profile: click.Path | None,
|
|
181
|
+
):
|
|
182
|
+
"""
|
|
183
|
+
Starts a Balsamic cases.
|
|
184
|
+
|
|
185
|
+
\b
|
|
186
|
+
Configures the case and creates the case config file:
|
|
187
|
+
- CASE_ID.json
|
|
188
|
+
and submits the job to slurm.
|
|
189
|
+
"""
|
|
190
|
+
factory = AnalysisStarterFactory(cg_config)
|
|
191
|
+
analysis_starter: AnalysisStarter = factory.get_analysis_starter_for_workflow(Workflow.BALSAMIC)
|
|
192
|
+
analysis_starter.start(case_id=case_id, workflow_profile=workflow_profile, panel_bed=panel_bed)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
@balsamic.command("start-available")
|
|
196
|
+
@click.pass_obj
|
|
197
|
+
def start_available(cg_config: CGConfig):
|
|
198
|
+
"""
|
|
199
|
+
Starts all available Balsamic cases.
|
|
200
|
+
|
|
201
|
+
\b
|
|
202
|
+
Configures the individual case and creates its case config file:
|
|
203
|
+
- CASE_ID.json
|
|
204
|
+
and submits the job to slurm.
|
|
205
|
+
"""
|
|
206
|
+
LOG.info("Starting Balsamic workflow for all available cases.")
|
|
207
|
+
factory = AnalysisStarterFactory(cg_config)
|
|
208
|
+
analysis_starter = factory.get_analysis_starter_for_workflow(Workflow.BALSAMIC)
|
|
209
|
+
succeeded: bool = analysis_starter.start_available()
|
|
210
|
+
if not succeeded:
|
|
211
|
+
raise click.Abort
|
|
@@ -1,59 +1,14 @@
|
|
|
1
1
|
import rich_click as click
|
|
2
2
|
|
|
3
|
-
from cg.constants.constants import GenomeVersion
|
|
4
|
-
from cg.constants.priority import SlurmQos
|
|
5
|
-
from cg.constants.subject import Sex
|
|
6
|
-
|
|
7
|
-
OPTION_GENDER = click.option(
|
|
8
|
-
"--gender",
|
|
9
|
-
type=click.Choice([Sex.FEMALE, Sex.MALE]),
|
|
10
|
-
required=False,
|
|
11
|
-
help="Case associated gender. Set this option to override the one selected by the customer in StatusDB.",
|
|
12
|
-
)
|
|
13
|
-
OPTION_GENOME_VERSION = click.option(
|
|
14
|
-
"--genome-version",
|
|
15
|
-
show_default=True,
|
|
16
|
-
default=GenomeVersion.HG19,
|
|
17
|
-
type=click.Choice([GenomeVersion.HG19, GenomeVersion.HG38, GenomeVersion.CANFAM3]),
|
|
18
|
-
help="Type and build version of the reference genome. Set this option to override the default.",
|
|
19
|
-
)
|
|
20
3
|
OPTION_PANEL_BED = click.option(
|
|
21
4
|
"--panel-bed",
|
|
22
5
|
required=False,
|
|
23
6
|
help="Panel BED is determined based on capture kit \
|
|
24
7
|
used for library prep. Set this option to override the default",
|
|
25
8
|
)
|
|
26
|
-
|
|
27
|
-
"-
|
|
28
|
-
"--slurm-quality-of-service",
|
|
29
|
-
type=click.Choice([SlurmQos.LOW, SlurmQos.NORMAL, SlurmQos.HIGH, SlurmQos.EXPRESS]),
|
|
30
|
-
help="Job priority in SLURM. Setting this option will override the StatusDB case priority.",
|
|
31
|
-
)
|
|
32
|
-
OPTION_PON_CNN = click.option(
|
|
33
|
-
"--pon-cnn",
|
|
34
|
-
type=click.Path(exists=True),
|
|
35
|
-
required=False,
|
|
36
|
-
help="Panel of normal reference (.cnn) for CNVkit",
|
|
37
|
-
)
|
|
38
|
-
OPTION_OBSERVATIONS = click.option(
|
|
39
|
-
"--observations",
|
|
40
|
-
type=click.Path(exists=True),
|
|
41
|
-
multiple=True,
|
|
42
|
-
required=False,
|
|
43
|
-
help="VCF paths of clinical and/or cancer SNVs and SVs observations (WHOLE_GENOME_SEQUENCING analysis only). Set this option to "
|
|
44
|
-
"override the latest Loqusdb dump files.",
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
OPTION_CACHE_VERSION = click.option(
|
|
48
|
-
"--cache-version",
|
|
49
|
-
type=click.STRING,
|
|
50
|
-
required=False,
|
|
51
|
-
help="Cache version to be used for init or analysis. Use 'develop' or 'X.X.X'.",
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
OPTION_CLUSTER_CONFIG = click.option(
|
|
55
|
-
"--cluster-config",
|
|
9
|
+
OPTION_WORKFLOW_PROFILE = click.option(
|
|
10
|
+
"--workflow-profile",
|
|
56
11
|
type=click.Path(exists=True),
|
|
57
12
|
required=False,
|
|
58
|
-
help="
|
|
13
|
+
help="Path to directory containing config.yaml.",
|
|
59
14
|
)
|