virtool-workflow 6.0.0a8__tar.gz → 6.0.0a9__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 (45) hide show
  1. {virtool_workflow-6.0.0a8 → virtool_workflow-6.0.0a9}/PKG-INFO +1 -1
  2. {virtool_workflow-6.0.0a8 → virtool_workflow-6.0.0a9}/pyproject.toml +2 -10
  3. {virtool_workflow-6.0.0a8 → virtool_workflow-6.0.0a9}/virtool_workflow/analysis/fastqc.py +15 -18
  4. {virtool_workflow-6.0.0a8 → virtool_workflow-6.0.0a9}/virtool_workflow/analysis/skewer.py +14 -21
  5. {virtool_workflow-6.0.0a8 → virtool_workflow-6.0.0a9}/virtool_workflow/analysis/trimming.py +3 -6
  6. {virtool_workflow-6.0.0a8 → virtool_workflow-6.0.0a9}/virtool_workflow/api/acquire.py +4 -6
  7. {virtool_workflow-6.0.0a8 → virtool_workflow-6.0.0a9}/virtool_workflow/api/client.py +11 -15
  8. {virtool_workflow-6.0.0a8 → virtool_workflow-6.0.0a9}/virtool_workflow/api/utils.py +9 -12
  9. {virtool_workflow-6.0.0a8 → virtool_workflow-6.0.0a9}/virtool_workflow/data/analyses.py +4 -9
  10. {virtool_workflow-6.0.0a8 → virtool_workflow-6.0.0a9}/virtool_workflow/data/hmms.py +5 -10
  11. {virtool_workflow-6.0.0a8 → virtool_workflow-6.0.0a9}/virtool_workflow/data/indexes.py +12 -21
  12. {virtool_workflow-6.0.0a8 → virtool_workflow-6.0.0a9}/virtool_workflow/data/jobs.py +2 -2
  13. {virtool_workflow-6.0.0a8 → virtool_workflow-6.0.0a9}/virtool_workflow/data/ml.py +16 -8
  14. {virtool_workflow-6.0.0a8 → virtool_workflow-6.0.0a9}/virtool_workflow/data/samples.py +8 -12
  15. {virtool_workflow-6.0.0a8 → virtool_workflow-6.0.0a9}/virtool_workflow/data/subtractions.py +11 -17
  16. {virtool_workflow-6.0.0a8 → virtool_workflow-6.0.0a9}/virtool_workflow/data/uploads.py +2 -3
  17. {virtool_workflow-6.0.0a8 → virtool_workflow-6.0.0a9}/virtool_workflow/decorators.py +2 -5
  18. {virtool_workflow-6.0.0a8 → virtool_workflow-6.0.0a9}/virtool_workflow/errors.py +1 -9
  19. {virtool_workflow-6.0.0a8 → virtool_workflow-6.0.0a9}/virtool_workflow/hooks.py +2 -4
  20. {virtool_workflow-6.0.0a8 → virtool_workflow-6.0.0a9}/virtool_workflow/pytest_plugin/data.py +3 -3
  21. {virtool_workflow-6.0.0a8 → virtool_workflow-6.0.0a9}/virtool_workflow/pytest_plugin/subprocess.py +1 -1
  22. {virtool_workflow-6.0.0a8 → virtool_workflow-6.0.0a9}/virtool_workflow/runtime/discover.py +5 -9
  23. {virtool_workflow-6.0.0a8 → virtool_workflow-6.0.0a9}/virtool_workflow/runtime/hook.py +4 -7
  24. {virtool_workflow-6.0.0a8 → virtool_workflow-6.0.0a9}/virtool_workflow/runtime/ping.py +1 -2
  25. {virtool_workflow-6.0.0a8 → virtool_workflow-6.0.0a9}/virtool_workflow/runtime/run.py +13 -16
  26. {virtool_workflow-6.0.0a8 → virtool_workflow-6.0.0a9}/virtool_workflow/runtime/run_subprocess.py +7 -10
  27. {virtool_workflow-6.0.0a8 → virtool_workflow-6.0.0a9}/virtool_workflow/runtime/sentry.py +2 -3
  28. {virtool_workflow-6.0.0a8 → virtool_workflow-6.0.0a9}/virtool_workflow/utils.py +12 -1
  29. {virtool_workflow-6.0.0a8 → virtool_workflow-6.0.0a9}/virtool_workflow/workflow.py +6 -10
  30. {virtool_workflow-6.0.0a8 → virtool_workflow-6.0.0a9}/LICENSE +0 -0
  31. {virtool_workflow-6.0.0a8 → virtool_workflow-6.0.0a9}/README.md +0 -0
  32. {virtool_workflow-6.0.0a8 → virtool_workflow-6.0.0a9}/virtool_workflow/__init__.py +0 -0
  33. {virtool_workflow-6.0.0a8 → virtool_workflow-6.0.0a9}/virtool_workflow/analysis/__init__.py +0 -0
  34. {virtool_workflow-6.0.0a8 → virtool_workflow-6.0.0a9}/virtool_workflow/analysis/utils.py +0 -0
  35. {virtool_workflow-6.0.0a8 → virtool_workflow-6.0.0a9}/virtool_workflow/api/__init__.py +0 -0
  36. {virtool_workflow-6.0.0a8 → virtool_workflow-6.0.0a9}/virtool_workflow/cli.py +0 -0
  37. {virtool_workflow-6.0.0a8 → virtool_workflow-6.0.0a9}/virtool_workflow/data/__init__.py +0 -0
  38. {virtool_workflow-6.0.0a8 → virtool_workflow-6.0.0a9}/virtool_workflow/files.py +0 -0
  39. {virtool_workflow-6.0.0a8 → virtool_workflow-6.0.0a9}/virtool_workflow/pytest_plugin/__init__.py +0 -0
  40. {virtool_workflow-6.0.0a8 → virtool_workflow-6.0.0a9}/virtool_workflow/pytest_plugin/utils.py +0 -0
  41. {virtool_workflow-6.0.0a8 → virtool_workflow-6.0.0a9}/virtool_workflow/runtime/__init__.py +0 -0
  42. {virtool_workflow-6.0.0a8 → virtool_workflow-6.0.0a9}/virtool_workflow/runtime/config.py +0 -0
  43. {virtool_workflow-6.0.0a8 → virtool_workflow-6.0.0a9}/virtool_workflow/runtime/events.py +0 -0
  44. {virtool_workflow-6.0.0a8 → virtool_workflow-6.0.0a9}/virtool_workflow/runtime/path.py +1 -1
  45. {virtool_workflow-6.0.0a8 → virtool_workflow-6.0.0a9}/virtool_workflow/runtime/redis.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: virtool-workflow
3
- Version: 6.0.0a8
3
+ Version: 6.0.0a9
4
4
  Summary: A framework for developing bioinformatics workflows for Virtool.
5
5
  Home-page: https://github.com/virtool/virtool-workflow
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "virtool-workflow"
3
- version = "6.0.0-alpha.8"
3
+ version = "6.0.0-alpha.9"
4
4
  description = "A framework for developing bioinformatics workflows for Virtool."
5
5
  authors = ["Ian Boyes", "Blake Smith", "Ryan Fang"]
6
6
  license = "MIT"
@@ -58,17 +58,9 @@ exclude = [
58
58
  ".ruff_cache",
59
59
  "__pypackages__",
60
60
  ]
61
- indent-width = 4
62
- line-length = 88
63
- target-version = "py310"
64
61
 
65
62
  [tool.ruff.lint]
66
- fixable = ["ALL", "I001"]
67
-
68
- [tool.ruff.format]
69
- indent-style = "space"
70
- line-ending = "auto"
71
- quote-style = "double"
63
+ select = ["ALL"]
72
64
 
73
65
  [build-system]
74
66
  requires = ["poetry-core>=1.0.0"]
@@ -2,12 +2,12 @@
2
2
  from __future__ import annotations
3
3
 
4
4
  import asyncio
5
- import statistics
6
5
  import shutil
6
+ import statistics
7
7
  import tempfile
8
8
  from dataclasses import dataclass
9
9
  from pathlib import Path
10
- from typing import Protocol, TextIO, IO
10
+ from typing import IO, Protocol, TextIO
11
11
 
12
12
  from pyfixtures import fixture
13
13
 
@@ -49,16 +49,16 @@ class BaseQualityParser:
49
49
  mean=statistics.mean([this.mean, other.mean]),
50
50
  median=statistics.mean([this.median, other.median]),
51
51
  lower_quartile=statistics.mean(
52
- [this.lower_quartile, other.lower_quartile]
52
+ [this.lower_quartile, other.lower_quartile],
53
53
  ),
54
54
  upper_quartile=statistics.mean(
55
- [this.upper_quartile, other.upper_quartile]
55
+ [this.upper_quartile, other.upper_quartile],
56
56
  ),
57
57
  tenth_percentile=statistics.mean(
58
- [this.tenth_percentile, other.tenth_percentile]
58
+ [this.tenth_percentile, other.tenth_percentile],
59
59
  ),
60
60
  ninetieth_percentile=statistics.mean(
61
- [this.ninetieth_percentile, other.ninetieth_percentile]
61
+ [this.ninetieth_percentile, other.ninetieth_percentile],
62
62
  ),
63
63
  )
64
64
  for this, other in zip(self.data, parser.data)
@@ -109,7 +109,7 @@ class BaseQualityParser:
109
109
  upper_quartile=upper_quartile,
110
110
  tenth_percentile=tenth_percentile,
111
111
  ninetieth_percentile=ninetieth_percentile,
112
- )
112
+ ),
113
113
  )
114
114
 
115
115
  if i - max_index != 1:
@@ -208,7 +208,7 @@ class NucleotideCompositionParser:
208
208
  split = line.split()
209
209
 
210
210
  try:
211
- g, a, t, c = [float(value) for value in split[1:]]
211
+ g, a, t, c = (float(value) for value in split[1:])
212
212
  except ValueError as err:
213
213
  if "NaN" not in str(err):
214
214
  raise
@@ -276,8 +276,7 @@ def _calculate_index_range(base: str) -> range:
276
276
 
277
277
 
278
278
  def _handle_base_quality_nan(split_line: list) -> list:
279
- """
280
- Parse a per-base quality line from FastQC containing NaN values.
279
+ """Parse a per-base quality line from FastQC containing NaN values.
281
280
 
282
281
  :param split_line: the quality line split into a :class:`.List`
283
282
  :return: replacement values
@@ -301,8 +300,7 @@ def _handle_base_quality_nan(split_line: list) -> list:
301
300
 
302
301
 
303
302
  def _parse_fastqc(fastqc_path: Path, output_path: Path) -> dict:
304
- """
305
- Parse the FastQC results at `fastqc_path`.
303
+ """Parse the FastQC results at `fastqc_path`.
306
304
 
307
305
  All FastQC data except the textual data file are removed.
308
306
 
@@ -333,7 +331,7 @@ def _parse_fastqc(fastqc_path: Path, output_path: Path) -> dict:
333
331
  nucleotide_composition = NucleotideCompositionParser()
334
332
  sequence_quality = SequenceQualityParser()
335
333
 
336
- with open(new_path, "r") as f:
334
+ with open(new_path) as f:
337
335
  while True:
338
336
  line = f.readline()
339
337
 
@@ -358,7 +356,7 @@ def _parse_fastqc(fastqc_path: Path, output_path: Path) -> dict:
358
356
  basic_statistics=basic_statistics,
359
357
  nucleotide_composition=nucleotide_composition,
360
358
  sequence_quality=sequence_quality,
361
- )
359
+ ),
362
360
  )
363
361
 
364
362
  if len(sides) == 1:
@@ -412,7 +410,7 @@ def _parse_fastqc(fastqc_path: Path, output_path: Path) -> dict:
412
410
  "composition": [
413
411
  [round(n, 1) for n in [point.g, point.a, point.t, point.c]]
414
412
  for point in left.nucleotide_composition.composite(
415
- right.nucleotide_composition
413
+ right.nucleotide_composition,
416
414
  ).data
417
415
  ],
418
416
  "count": basic.count,
@@ -432,14 +430,13 @@ class FastQCRunner(Protocol):
432
430
 
433
431
  @fixture
434
432
  async def fastqc(run_subprocess: RunSubprocess):
435
- """
436
- Provides an asynchronous function that can run FastQC as a subprocess.
433
+ """Provides an asynchronous function that can run FastQC as a subprocess.
437
434
 
438
435
  The function takes a one or two paths to FASTQ read files (:class:`.ReadPaths`) in
439
436
  a tuple.
440
437
 
441
438
  Example:
442
-
439
+ -------
443
440
  .. code-block:: python
444
441
 
445
442
  @step
@@ -1,5 +1,5 @@
1
- """
2
- Utilities and a fixture for using `Skewer <https://github.com/relipmoc/skewer>`_ to trim reads.
1
+ """Utilities and a fixture for using `Skewer <https://github.com/relipmoc/skewer>`_ to
2
+ trim reads.
3
3
  """
4
4
  import asyncio
5
5
  import os
@@ -85,18 +85,16 @@ class SkewerResult:
85
85
 
86
86
  @property
87
87
  def left(self) -> Path:
88
- """
89
- The path to one of:
90
- - the FASTQ trimming result for an unpaired Illumina dataset
91
- - the FASTA trimming result for the left reads of a paired Illumina dataset
88
+ """The path to one of:
89
+ - the FASTQ trimming result for an unpaired Illumina dataset
90
+ - the FASTA trimming result for the left reads of a paired Illumina dataset
92
91
 
93
92
  """
94
93
  return self.read_paths[0]
95
94
 
96
95
  @property
97
96
  def right(self) -> Path | None:
98
- """
99
- The path to the rights reads of a paired Illumina dataset.
97
+ """The path to the rights reads of a paired Illumina dataset.
100
98
 
101
99
  ``None`` if the dataset in unpaired.
102
100
 
@@ -110,16 +108,14 @@ class SkewerResult:
110
108
 
111
109
 
112
110
  def calculate_skewer_trimming_parameters(
113
- sample: WFSample, min_read_length: int
111
+ sample: WFSample, min_read_length: int,
114
112
  ) -> SkewerConfiguration:
115
- """
116
- Calculates trimming parameters based on the library type, and minimum allowed trim length.
113
+ """Calculates trimming parameters based on the library type, and minimum allowed trim length.
117
114
 
118
115
  :param sample: The sample to calculate trimming parameters for.
119
116
  :param min_read_length: The minimum length of a read before it is discarded.
120
117
  :return: the trimming parameters
121
118
  """
122
-
123
119
  config = SkewerConfiguration(
124
120
  min_length=min_read_length,
125
121
  mode=SkewerMode.PAIRED_END if sample.paired else SkewerMode.SINGLE_END,
@@ -145,15 +141,14 @@ class SkewerRunner(Protocol):
145
141
  """A protocol describing callables that can be used to run Skewer."""
146
142
 
147
143
  async def __call__(
148
- self, config: SkewerConfiguration, paths: ReadPaths, output_path: Path
144
+ self, config: SkewerConfiguration, paths: ReadPaths, output_path: Path,
149
145
  ) -> SkewerResult:
150
146
  ...
151
147
 
152
148
 
153
149
  @fixture
154
150
  def skewer(proc: int, run_subprocess: RunSubprocess) -> SkewerRunner:
155
- """
156
- Provides an asynchronous function that can run skewer.
151
+ """Provides an asynchronous function that can run skewer.
157
152
 
158
153
  The provided function takes a :class:`.SkewerConfiguration` and a tuple of paths to
159
154
  the left and right reads to trim. If a single member tuple is provided, the dataset
@@ -163,7 +158,7 @@ def skewer(proc: int, run_subprocess: RunSubprocess) -> SkewerRunner:
163
158
  for the workflow run.
164
159
 
165
160
  Example:
166
-
161
+ -------
167
162
  .. code-block:: python
168
163
 
169
164
  @step
@@ -183,7 +178,7 @@ def skewer(proc: int, run_subprocess: RunSubprocess) -> SkewerRunner:
183
178
  raise RuntimeError("skewer is not installed.")
184
179
 
185
180
  async def func(
186
- config: SkewerConfiguration, read_paths: ReadPaths, output_path: Path
181
+ config: SkewerConfiguration, read_paths: ReadPaths, output_path: Path,
187
182
  ):
188
183
  temp_path = Path(await asyncio.to_thread(mkdtemp, suffix="_virtool_skewer"))
189
184
 
@@ -224,7 +219,7 @@ def skewer(proc: int, run_subprocess: RunSubprocess) -> SkewerRunner:
224
219
  )
225
220
 
226
221
  read_paths = await asyncio.to_thread(
227
- _rename_trimming_results, temp_path, output_path
222
+ _rename_trimming_results, temp_path, output_path,
228
223
  )
229
224
 
230
225
  return SkewerResult(command, output_path, process, read_paths)
@@ -233,12 +228,10 @@ def skewer(proc: int, run_subprocess: RunSubprocess) -> SkewerRunner:
233
228
 
234
229
 
235
230
  def _rename_trimming_results(temp_path: Path, output_path: Path) -> ReadPaths:
236
- """
237
- Rename Skewer output to a simple name used in Virtool.
231
+ """Rename Skewer output to a simple name used in Virtool.
238
232
 
239
233
  :param path: The path containing the results from Skewer
240
234
  """
241
-
242
235
  shutil.move(
243
236
  temp_path / "reads-trimmed.log",
244
237
  output_path / "trim.log",
@@ -8,10 +8,9 @@ from virtool_workflow.data.samples import WFSample
8
8
 
9
9
 
10
10
  def calculate_trimming_cache_key(
11
- sample_id: str, trimming_parameters: dict, program: str = "skewer"
11
+ sample_id: str, trimming_parameters: dict, program: str = "skewer",
12
12
  ):
13
- """
14
- Compute a unique cache key.
13
+ """Compute a unique cache key.
15
14
 
16
15
  **This is not currently used.**
17
16
 
@@ -21,7 +20,6 @@ def calculate_trimming_cache_key(
21
20
  :return: A unique cache key.
22
21
 
23
22
  """
24
-
25
23
  raw_key = "reads-" + json.dumps(
26
24
  {
27
25
  "id": sample_id,
@@ -35,8 +33,7 @@ def calculate_trimming_cache_key(
35
33
 
36
34
 
37
35
  def calculate_trimming_min_length(sample: WFSample) -> int:
38
- """
39
- Calculate the minimum trimming length that should be used for the passed sample.
36
+ """Calculate the minimum trimming length that should be used for the passed sample.
40
37
 
41
38
  This takes into account the library type (:class:`.LibraryType`) and the maximum
42
39
  observed read length in the sample.
@@ -1,10 +1,10 @@
1
1
  import asyncio
2
2
 
3
- from aiohttp import ClientSession, TCPConnector, ClientConnectionError
3
+ from aiohttp import ClientConnectionError, ClientSession, TCPConnector
4
4
  from structlog import get_logger
5
5
  from virtool_core.models.job import JobAcquired
6
6
 
7
- from virtool_workflow.errors import JobAlreadyAcquired, JobsAPIServerError, JobsAPIError
7
+ from virtool_workflow.errors import JobAlreadyAcquired, JobsAPIError, JobsAPIServerError
8
8
 
9
9
  logger = get_logger("api")
10
10
 
@@ -13,16 +13,14 @@ async def acquire_job_by_id(
13
13
  jobs_api_connection_string: str,
14
14
  job_id: str,
15
15
  ) -> JobAcquired:
16
- """
17
- Acquire the job with a given ID via the API.
16
+ """Acquire the job with a given ID via the API.
18
17
 
19
18
  :param jobs_api_connection_string: The url for the jobs API.
20
19
  :param job_id: The id of the job to acquire
21
20
  :return: a job including its API key
22
21
  """
23
-
24
22
  async with ClientSession(
25
- connector=TCPConnector(force_close=True, limit=100)
23
+ connector=TCPConnector(force_close=True, limit=100),
26
24
  ) as session:
27
25
  attempts = 4
28
26
 
@@ -2,14 +2,14 @@ from contextlib import asynccontextmanager
2
2
  from pathlib import Path
3
3
 
4
4
  import aiofiles
5
- from aiohttp import ClientSession, BasicAuth
5
+ from aiohttp import BasicAuth, ClientSession
6
6
 
7
7
  from virtool_workflow.api.utils import (
8
- raise_exception_by_status_code,
9
8
  decode_json_response,
9
+ raise_exception_by_status_code,
10
10
  )
11
- from virtool_workflow.files import VirtoolFileFormat
12
11
  from virtool_workflow.errors import JobsAPIError
12
+ from virtool_workflow.files import VirtoolFileFormat
13
13
 
14
14
  CHUNK_SIZE = 1024 * 1024 * 2
15
15
 
@@ -21,19 +21,17 @@ class APIClient:
21
21
 
22
22
  async def get_json(self, path: str) -> dict:
23
23
  """Get the JSON response from the provided API ``path``."""
24
-
25
24
  async with self.http.get(f"{self.jobs_api_connection_string}{path}") as resp:
26
25
  await raise_exception_by_status_code(resp)
27
26
  return await decode_json_response(resp)
28
27
 
29
28
  async def get_file(self, path: str, target_path: Path):
30
- """
31
- Download the file at URL ``path`` to the local filesystem path ``target_path``.
29
+ """Download the file at URL ``path`` to the local filesystem path ``target_path``.
32
30
  """
33
31
  async with self.http.get(f"{self.jobs_api_connection_string}{path}") as resp:
34
32
  if resp.status != 200:
35
33
  raise JobsAPIError(
36
- f"Encountered {resp.status} while downloading '{path}'"
34
+ f"Encountered {resp.status} while downloading '{path}'",
37
35
  )
38
36
  async with aiofiles.open(target_path, "wb") as f:
39
37
  async for chunk in resp.content.iter_chunked(CHUNK_SIZE):
@@ -42,8 +40,7 @@ class APIClient:
42
40
  return target_path
43
41
 
44
42
  async def patch_json(self, path: str, data: dict) -> dict:
45
- """
46
- Make a patch request against the provided API ``path`` and return the response
43
+ """Make a patch request against the provided API ``path`` and return the response
47
44
  as a dictionary of decoded JSON.
48
45
 
49
46
  :param path: the API path to make the request against
@@ -51,7 +48,7 @@ class APIClient:
51
48
  :return: the response as a dictionary of decoded JSON
52
49
  """
53
50
  async with self.http.patch(
54
- f"{self.jobs_api_connection_string}{path}", json=data
51
+ f"{self.jobs_api_connection_string}{path}", json=data,
55
52
  ) as resp:
56
53
  await raise_exception_by_status_code(resp)
57
54
  return await decode_json_response(resp)
@@ -78,7 +75,7 @@ class APIClient:
78
75
 
79
76
  async def post_json(self, path: str, data: dict) -> dict:
80
77
  async with self.http.post(
81
- f"{self.jobs_api_connection_string}{path}", json=data
78
+ f"{self.jobs_api_connection_string}{path}", json=data,
82
79
  ) as resp:
83
80
  await raise_exception_by_status_code(resp)
84
81
  return await decode_json_response(resp)
@@ -105,7 +102,7 @@ class APIClient:
105
102
 
106
103
  async def put_json(self, path: str, data: dict) -> dict:
107
104
  async with self.http.put(
108
- f"{self.jobs_api_connection_string}{path}", json=data
105
+ f"{self.jobs_api_connection_string}{path}", json=data,
109
106
  ) as resp:
110
107
  await raise_exception_by_status_code(resp)
111
108
  return await decode_json_response(resp)
@@ -127,10 +124,9 @@ async def api_client(
127
124
  job_id: str,
128
125
  key: str,
129
126
  ):
130
- """
131
- An authenticated :class:``APIClient`` to make requests against the jobs API.
127
+ """An authenticated :class:``APIClient`` to make requests against the jobs API.
132
128
  """
133
129
  async with ClientSession(
134
- auth=BasicAuth(login=f"job-{job_id}", password=key)
130
+ auth=BasicAuth(login=f"job-{job_id}", password=key),
135
131
  ) as http:
136
132
  yield APIClient(http, jobs_api_connection_string)
@@ -2,27 +2,26 @@ import asyncio
2
2
  import functools
3
3
 
4
4
  from aiohttp import (
5
- ServerDisconnectedError,
6
5
  ClientConnectorError,
7
6
  ClientResponse,
8
7
  ContentTypeError,
8
+ ServerDisconnectedError,
9
9
  )
10
10
  from structlog import get_logger
11
11
 
12
12
  from virtool_workflow.errors import (
13
- JobsAPIServerError,
14
- JobsAPINotFound,
15
13
  JobsAPIBadRequest,
16
- JobsAPIForbidden,
17
14
  JobsAPIConflict,
15
+ JobsAPIForbidden,
16
+ JobsAPINotFound,
17
+ JobsAPIServerError,
18
18
  )
19
19
 
20
20
  logger = get_logger("api")
21
21
 
22
22
 
23
23
  def retry(func):
24
- """
25
- Retry an API call five times when encountering the following exceptions:
24
+ """Retry an API call five times when encountering the following exceptions:
26
25
  * ``ConnectionRefusedError``.
27
26
  * ``ClientConnectorError``.
28
27
  * ``ServerDisconnectedError``.
@@ -47,7 +46,7 @@ def retry(func):
47
46
 
48
47
  attempts += 1
49
48
  get_logger("runtime").info(
50
- f"Encountered {type(err).__name__}. Retrying in 5 seconds."
49
+ f"Encountered {type(err).__name__}. Retrying in 5 seconds.",
51
50
  )
52
51
  await asyncio.sleep(5)
53
52
 
@@ -57,8 +56,7 @@ def retry(func):
57
56
 
58
57
 
59
58
  async def decode_json_response(resp: ClientResponse) -> dict | list | None:
60
- """
61
- Decode a JSON response from a :class:``ClientResponse``.
59
+ """Decode a JSON response from a :class:``ClientResponse``.
62
60
 
63
61
  Raise a :class:`ValueError` if the response is not JSON.
64
62
 
@@ -72,8 +70,7 @@ async def decode_json_response(resp: ClientResponse) -> dict | list | None:
72
70
 
73
71
 
74
72
  async def raise_exception_by_status_code(resp: ClientResponse):
75
- """
76
- Raise an exception based on the status code of the response.
73
+ """Raise an exception based on the status code of the response.
77
74
 
78
75
  :param resp: the response to check
79
76
  :raise JobsAPIBadRequest: the response status code is 400
@@ -108,5 +105,5 @@ async def raise_exception_by_status_code(resp: ClientResponse):
108
105
  raise status_exception_map[resp.status](message)
109
106
  else:
110
107
  raise ValueError(
111
- f"Status code {resp.status} not handled for response\n {resp}"
108
+ f"Status code {resp.status} not handled for response\n {resp}",
112
109
  )
@@ -52,17 +52,14 @@ class WFAnalysis:
52
52
  """The workflow being run to populate the analysis."""
53
53
 
54
54
  async def delete(self):
55
- """
56
- Delete the analysis.
55
+ """Delete the analysis.
57
56
 
58
57
  This method should be called if the workflow fails before a result is uploaded.
59
58
  """
60
-
61
59
  await self._api.delete(f"/analyses/{self.id}")
62
60
 
63
61
  async def upload_file(self, path: Path, fmt: VirtoolFileFormat = "unknown"):
64
- """
65
- Upload files in the workflow environment that should be associated with the
62
+ """Upload files in the workflow environment that should be associated with the
66
63
  current analysis.
67
64
 
68
65
  :param path: the path to the file to upload
@@ -76,8 +73,7 @@ class WFAnalysis:
76
73
  )
77
74
 
78
75
  async def upload_result(self, results: dict[str, Any]):
79
- """
80
- Upload the results dict for the analysis.
76
+ """Upload the results dict for the analysis.
81
77
 
82
78
  :param results: the analysis results
83
79
  """
@@ -89,8 +85,7 @@ async def analysis(
89
85
  _api: APIClient,
90
86
  job: Job,
91
87
  ) -> WFAnalysis:
92
- """
93
- A :class:`.WFAnalysis` object that represents the analysis associated with the running
88
+ """A :class:`.WFAnalysis` object that represents the analysis associated with the running
94
89
  workflow.
95
90
  """
96
91
  id_ = job.args["analysis_id"]
@@ -17,8 +17,7 @@ from virtool_workflow.runtime.run_subprocess import RunSubprocess
17
17
 
18
18
  @dataclass
19
19
  class WFHMMs:
20
- """
21
- A class that exposes:
20
+ """A class that exposes:
22
21
 
23
22
  1. A :class:`dict` the links `HMMER <http://hmmer.org/>`_ cluster IDs to Virtool
24
23
  annotation IDs.
@@ -37,24 +36,21 @@ class WFHMMs:
37
36
 
38
37
  @cached_property
39
38
  def cluster_annotation_map(self) -> dict[int, str]:
40
- """
41
- A :class:`dict` that maps cluster IDs used to identify HMMs in
39
+ """A :class:`dict` that maps cluster IDs used to identify HMMs in
42
40
  `HMMER <http://hmmer.org/>`_ to annotation IDs used in Virtool.
43
41
  """
44
42
  return {hmm.cluster: hmm.id for hmm in self.annotations}
45
43
 
46
44
  @property
47
45
  def profiles_path(self):
48
- """
49
- The path to the ``profiles.hmm`` file.
46
+ """The path to the ``profiles.hmm`` file.
50
47
 
51
48
  It can be provided directly to HMMER.
52
49
  """
53
50
  return self.path / "profiles.hmm"
54
51
 
55
52
  def get_id_by_cluster(self, cluster: int) -> str:
56
- """
57
- Get the Virtool HMM annotation ID for a given cluster ID.
53
+ """Get the Virtool HMM annotation ID for a given cluster ID.
58
54
 
59
55
  :param cluster: a cluster ID
60
56
  :return: the corresponding annotation ID
@@ -69,8 +65,7 @@ async def hmms(
69
65
  run_subprocess: RunSubprocess,
70
66
  work_path: Path,
71
67
  ):
72
- """
73
- A fixture for accessing HMM data.
68
+ """A fixture for accessing HMM data.
74
69
 
75
70
  The ``*.hmm`` file is copied from the data directory and ``hmmpress`` is run to
76
71
  create all the HMM files.
@@ -13,8 +13,8 @@ from virtool_core.models.reference import ReferenceNested
13
13
  from virtool_core.utils import decompress_file
14
14
 
15
15
  from virtool_workflow.api.client import APIClient
16
- from virtool_workflow.files import VirtoolFileFormat
17
16
  from virtool_workflow.errors import MissingJobArgument
17
+ from virtool_workflow.files import VirtoolFileFormat
18
18
 
19
19
  logger = get_logger("api")
20
20
 
@@ -43,16 +43,14 @@ class WFIndex:
43
43
 
44
44
  @property
45
45
  def bowtie_path(self) -> Path:
46
- """
47
- The path to the Bowtie2 index prefix for the Virtool index.
46
+ """The path to the Bowtie2 index prefix for the Virtool index.
48
47
 
49
48
  """
50
49
  return self.path / "reference"
51
50
 
52
51
  @property
53
52
  def fasta_path(self) -> Path:
54
- """
55
- The path to the complete FASTA file for the reference index in the workflow's
53
+ """The path to the complete FASTA file for the reference index in the workflow's
56
54
  work directory.
57
55
 
58
56
  """
@@ -60,16 +58,14 @@ class WFIndex:
60
58
 
61
59
  @property
62
60
  def json_path(self) -> Path:
63
- """
64
- The path to the JSON representation of the reference index in the workflow's
61
+ """The path to the JSON representation of the reference index in the workflow's
65
62
  work directory.
66
63
 
67
64
  """
68
65
  return self.path / "otus.json"
69
66
 
70
67
  def get_otu_id_by_sequence_id(self, sequence_id: str) -> str:
71
- """
72
- Get the ID of the parent OTU for the given ``sequence_id``.
68
+ """Get the ID of the parent OTU for the given ``sequence_id``.
73
69
 
74
70
  :param sequence_id: the sequence ID
75
71
  :return: the matching OTU ID
@@ -81,8 +77,7 @@ class WFIndex:
81
77
  raise ValueError("The sequence_id does not exist in the index")
82
78
 
83
79
  def get_sequence_length(self, sequence_id: str) -> int:
84
- """
85
- Get the sequence length for the given ``sequence_id``.
80
+ """Get the sequence length for the given ``sequence_id``.
86
81
 
87
82
  :param sequence_id: the sequence ID
88
83
  :return: the length of the sequence
@@ -98,8 +93,7 @@ class WFIndex:
98
93
  otu_ids: list[str],
99
94
  path: Path,
100
95
  ) -> dict[str, int]:
101
- """
102
- Generate a FASTA file for all the isolates of the OTUs specified by ``otu_ids``.
96
+ """Generate a FASTA file for all the isolates of the OTUs specified by ``otu_ids``.
103
97
 
104
98
  :param otu_ids: the list of OTU IDs for which to generate and index
105
99
  :param path: the path to the reference index directory
@@ -157,10 +151,9 @@ class WFNewIndex:
157
151
  await self._api.patch_json(f"/indexes/{self.id}", {})
158
152
 
159
153
  async def upload(
160
- self, path: Path, fmt: VirtoolFileFormat = "fasta", name: str | None = None
154
+ self, path: Path, fmt: VirtoolFileFormat = "fasta", name: str | None = None,
161
155
  ):
162
- """
163
- Upload a file to associate with the index being built.
156
+ """Upload a file to associate with the index being built.
164
157
 
165
158
  Allowed file names are:
166
159
 
@@ -186,8 +179,7 @@ class WFNewIndex:
186
179
 
187
180
  @property
188
181
  def otus_json_path(self) -> Path:
189
- """
190
- The path to the JSON representation of the reference index in the workflow's
182
+ """The path to the JSON representation of the reference index in the workflow's
191
183
  work directory.
192
184
 
193
185
  """
@@ -280,10 +272,9 @@ async def index(
280
272
 
281
273
  @fixture
282
274
  async def new_index(
283
- _api: APIClient, job: Job, proc: int, work_path: Path
275
+ _api: APIClient, job: Job, proc: int, work_path: Path,
284
276
  ) -> WFNewIndex:
285
- """
286
- The :class:`.WFNewIndex` for an index being created by the current job.
277
+ """The :class:`.WFNewIndex` for an index being created by the current job.
287
278
  """
288
279
  try:
289
280
  id_ = job.args["index_id"]
@@ -2,9 +2,9 @@ import traceback
2
2
 
3
3
  from pyfixtures import fixture
4
4
  from structlog import get_logger
5
- from virtool_core.models.job import JobState, JobAcquired, Job
5
+ from virtool_core.models.job import Job, JobAcquired, JobState
6
6
 
7
- from virtool_workflow import WorkflowStep, Workflow
7
+ from virtool_workflow import Workflow, WorkflowStep
8
8
  from virtool_workflow.api.client import APIClient
9
9
 
10
10
  MAX_TB = 50