virtool-workflow 6.0.0a11__tar.gz → 7.1.0__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 (48) hide show
  1. virtool_workflow-7.1.0/PKG-INFO +74 -0
  2. virtool_workflow-7.1.0/README.md +49 -0
  3. {virtool_workflow-6.0.0a11 → virtool_workflow-7.1.0}/pyproject.toml +33 -14
  4. {virtool_workflow-6.0.0a11 → virtool_workflow-7.1.0}/virtool_workflow/api/acquire.py +6 -2
  5. {virtool_workflow-6.0.0a11 → virtool_workflow-7.1.0}/virtool_workflow/api/utils.py +8 -8
  6. {virtool_workflow-6.0.0a11 → virtool_workflow-7.1.0}/virtool_workflow/data/indexes.py +12 -9
  7. {virtool_workflow-6.0.0a11 → virtool_workflow-7.1.0}/virtool_workflow/data/samples.py +15 -7
  8. {virtool_workflow-6.0.0a11 → virtool_workflow-7.1.0}/virtool_workflow/data/subtractions.py +13 -6
  9. {virtool_workflow-6.0.0a11 → virtool_workflow-7.1.0}/virtool_workflow/decorators.py +7 -5
  10. virtool_workflow-7.1.0/virtool_workflow/errors.py +62 -0
  11. virtool_workflow-7.1.0/virtool_workflow/files.py +40 -0
  12. {virtool_workflow-6.0.0a11 → virtool_workflow-7.1.0}/virtool_workflow/hooks.py +2 -3
  13. {virtool_workflow-6.0.0a11 → virtool_workflow-7.1.0}/virtool_workflow/pytest_plugin/data.py +0 -1
  14. {virtool_workflow-6.0.0a11 → virtool_workflow-7.1.0}/virtool_workflow/runtime/redis.py +19 -14
  15. {virtool_workflow-6.0.0a11 → virtool_workflow-7.1.0}/virtool_workflow/runtime/run.py +5 -5
  16. {virtool_workflow-6.0.0a11 → virtool_workflow-7.1.0}/virtool_workflow/runtime/run_subprocess.py +35 -10
  17. {virtool_workflow-6.0.0a11 → virtool_workflow-7.1.0}/virtool_workflow/runtime/sentry.py +4 -4
  18. {virtool_workflow-6.0.0a11 → virtool_workflow-7.1.0}/virtool_workflow/utils.py +9 -0
  19. virtool_workflow-6.0.0a11/PKG-INFO +0 -119
  20. virtool_workflow-6.0.0a11/README.md +0 -93
  21. virtool_workflow-6.0.0a11/virtool_workflow/errors.py +0 -61
  22. virtool_workflow-6.0.0a11/virtool_workflow/files.py +0 -24
  23. {virtool_workflow-6.0.0a11 → virtool_workflow-7.1.0}/LICENSE +0 -0
  24. {virtool_workflow-6.0.0a11 → virtool_workflow-7.1.0}/virtool_workflow/__init__.py +0 -0
  25. {virtool_workflow-6.0.0a11 → virtool_workflow-7.1.0}/virtool_workflow/analysis/__init__.py +0 -0
  26. {virtool_workflow-6.0.0a11 → virtool_workflow-7.1.0}/virtool_workflow/analysis/fastqc.py +0 -0
  27. {virtool_workflow-6.0.0a11 → virtool_workflow-7.1.0}/virtool_workflow/analysis/skewer.py +0 -0
  28. {virtool_workflow-6.0.0a11 → virtool_workflow-7.1.0}/virtool_workflow/analysis/trimming.py +0 -0
  29. {virtool_workflow-6.0.0a11 → virtool_workflow-7.1.0}/virtool_workflow/analysis/utils.py +0 -0
  30. {virtool_workflow-6.0.0a11 → virtool_workflow-7.1.0}/virtool_workflow/api/__init__.py +0 -0
  31. {virtool_workflow-6.0.0a11 → virtool_workflow-7.1.0}/virtool_workflow/api/client.py +0 -0
  32. {virtool_workflow-6.0.0a11 → virtool_workflow-7.1.0}/virtool_workflow/cli.py +0 -0
  33. {virtool_workflow-6.0.0a11 → virtool_workflow-7.1.0}/virtool_workflow/data/__init__.py +0 -0
  34. {virtool_workflow-6.0.0a11 → virtool_workflow-7.1.0}/virtool_workflow/data/analyses.py +0 -0
  35. {virtool_workflow-6.0.0a11 → virtool_workflow-7.1.0}/virtool_workflow/data/hmms.py +0 -0
  36. {virtool_workflow-6.0.0a11 → virtool_workflow-7.1.0}/virtool_workflow/data/jobs.py +0 -0
  37. {virtool_workflow-6.0.0a11 → virtool_workflow-7.1.0}/virtool_workflow/data/ml.py +0 -0
  38. {virtool_workflow-6.0.0a11 → virtool_workflow-7.1.0}/virtool_workflow/data/uploads.py +0 -0
  39. {virtool_workflow-6.0.0a11 → virtool_workflow-7.1.0}/virtool_workflow/pytest_plugin/__init__.py +0 -0
  40. {virtool_workflow-6.0.0a11 → virtool_workflow-7.1.0}/virtool_workflow/pytest_plugin/utils.py +0 -0
  41. {virtool_workflow-6.0.0a11 → virtool_workflow-7.1.0}/virtool_workflow/runtime/__init__.py +0 -0
  42. {virtool_workflow-6.0.0a11 → virtool_workflow-7.1.0}/virtool_workflow/runtime/config.py +0 -0
  43. {virtool_workflow-6.0.0a11 → virtool_workflow-7.1.0}/virtool_workflow/runtime/discover.py +0 -0
  44. {virtool_workflow-6.0.0a11 → virtool_workflow-7.1.0}/virtool_workflow/runtime/events.py +0 -0
  45. {virtool_workflow-6.0.0a11 → virtool_workflow-7.1.0}/virtool_workflow/runtime/hook.py +0 -0
  46. {virtool_workflow-6.0.0a11 → virtool_workflow-7.1.0}/virtool_workflow/runtime/path.py +0 -0
  47. {virtool_workflow-6.0.0a11 → virtool_workflow-7.1.0}/virtool_workflow/runtime/ping.py +0 -0
  48. {virtool_workflow-6.0.0a11 → virtool_workflow-7.1.0}/virtool_workflow/workflow.py +0 -0
@@ -0,0 +1,74 @@
1
+ Metadata-Version: 2.1
2
+ Name: virtool-workflow
3
+ Version: 7.1.0
4
+ Summary: A framework for developing bioinformatics workflows for Virtool.
5
+ Home-page: https://github.com/virtool/virtool-workflow
6
+ License: MIT
7
+ Author: Ian Boyes
8
+ Maintainer: Ian Boyes
9
+ Requires-Python: >=3.12,<3.13
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Requires-Dist: aiofiles (>=0.7.0,<0.8.0)
14
+ Requires-Dist: aiohttp (>=3.8.1,<4.0.0)
15
+ Requires-Dist: biopython (>=1.81,<2.0)
16
+ Requires-Dist: click (>=8.1.7,<9.0.0)
17
+ Requires-Dist: orjson (>=3.9.9,<4.0.0)
18
+ Requires-Dist: pydantic-factories (>=1.17.3,<2.0.0)
19
+ Requires-Dist: pyfixtures (>=1.0.0,<2.0.0)
20
+ Requires-Dist: sentry-sdk (>=2.3.1,<3.0.0)
21
+ Requires-Dist: virtool-core (>=14.0.0,<15.0.0)
22
+ Project-URL: Repository, https://github.com/virtool/virtool-workflow
23
+ Description-Content-Type: text/markdown
24
+
25
+ # Virtool Workflow
26
+
27
+ ![Tests](https://github.com/virtool/virtool-workflow/workflows/ci/badge.svg?branch=main)
28
+ [![PyPI version](https://badge.fury.io/py/virtool-workflow.svg)](https://badge.fury.io/py/virtool-workflow)
29
+
30
+ A framework for developing bioinformatic workflows in Python.
31
+
32
+ ```python
33
+ from virtool_workflow import step
34
+
35
+
36
+ @step
37
+ def step_function():
38
+ ...
39
+
40
+
41
+ @step
42
+ def step_function_2():
43
+ ...
44
+ ```
45
+
46
+ ## Contributing
47
+
48
+ ### Commits
49
+
50
+ We require specific commit formatting. Any commit that does not follow the guidelines
51
+ will be squashed at our discretion.
52
+
53
+ Read our [commit and release](https://dev.virtool.ca/en/latest/commits_releases.html)
54
+ documentation for more information.
55
+
56
+ ### Tests
57
+
58
+ Run tests with:
59
+
60
+ ```shell
61
+ # Bring up Redis and the test container.
62
+ docker compose up -d
63
+
64
+ # Run tests in the test container.
65
+ docker compose exec test poetry run pytest
66
+
67
+ ```
68
+
69
+ Run specific tests like:
70
+
71
+ ```shell
72
+ docker compose exec test poetry run pytest tests/test_status.py
73
+ ```
74
+
@@ -0,0 +1,49 @@
1
+ # Virtool Workflow
2
+
3
+ ![Tests](https://github.com/virtool/virtool-workflow/workflows/ci/badge.svg?branch=main)
4
+ [![PyPI version](https://badge.fury.io/py/virtool-workflow.svg)](https://badge.fury.io/py/virtool-workflow)
5
+
6
+ A framework for developing bioinformatic workflows in Python.
7
+
8
+ ```python
9
+ from virtool_workflow import step
10
+
11
+
12
+ @step
13
+ def step_function():
14
+ ...
15
+
16
+
17
+ @step
18
+ def step_function_2():
19
+ ...
20
+ ```
21
+
22
+ ## Contributing
23
+
24
+ ### Commits
25
+
26
+ We require specific commit formatting. Any commit that does not follow the guidelines
27
+ will be squashed at our discretion.
28
+
29
+ Read our [commit and release](https://dev.virtool.ca/en/latest/commits_releases.html)
30
+ documentation for more information.
31
+
32
+ ### Tests
33
+
34
+ Run tests with:
35
+
36
+ ```shell
37
+ # Bring up Redis and the test container.
38
+ docker compose up -d
39
+
40
+ # Run tests in the test container.
41
+ docker compose exec test poetry run pytest
42
+
43
+ ```
44
+
45
+ Run specific tests like:
46
+
47
+ ```shell
48
+ docker compose exec test poetry run pytest tests/test_status.py
49
+ ```
@@ -1,32 +1,44 @@
1
1
  [tool.poetry]
2
2
  name = "virtool-workflow"
3
- version = "6.0.0-alpha.11"
3
+ version = "7.1.0"
4
4
  description = "A framework for developing bioinformatics workflows for Virtool."
5
- authors = ["Ian Boyes", "Blake Smith", "Ryan Fang"]
5
+ authors = [
6
+ "Ian Boyes",
7
+ "Blake Smith",
8
+ "Ryan Fang",
9
+ "Matt Curtis",
10
+ "Aman Monga",
11
+ "Bryce Davidson",
12
+ "Christine Wong Chong",
13
+ "Lilly Roberts",
14
+ "Markus Swoveland",
15
+ "Ryan Fang",
16
+ ]
6
17
  license = "MIT"
18
+ maintainers = [
19
+ "Ian Boyes",
20
+ "Reece Hoffman"
21
+ ]
7
22
  readme = "README.md"
8
23
  repository = "https://github.com/virtool/virtool-workflow"
9
24
  classifiers = [
10
- "Topic :: Software Development :: Libraries",
11
- "Programming Language :: Python :: 3.10"
25
+ "Programming Language :: Python :: 3.12",
12
26
  ]
13
27
  packages = [
14
28
  { include = "virtool_workflow" },
15
29
  ]
16
30
 
17
31
  [tool.poetry.dependencies]
18
- python = "~3.10"
19
- click = "^8.1.7"
20
- aiohttp = "^3.8.1"
32
+ python = "~3.12"
21
33
  aiofiles = "^0.7.0"
22
- virtool-core = "^11.0.0"
23
- aioredis = "1.3.1"
24
- sentry-sdk = "^1.5.7"
25
- pyfixtures = "^1.0.0"
26
- orjson = "^3.9.9"
34
+ aiohttp = "^3.8.1"
27
35
  biopython = "^1.81"
36
+ click = "^8.1.7"
37
+ orjson = "^3.9.9"
28
38
  pydantic-factories = "^1.17.3"
29
-
39
+ pyfixtures = "^1.0.0"
40
+ sentry-sdk = "^2.3.1"
41
+ virtool-core = "^14.0.0"
30
42
 
31
43
  [tool.poetry.scripts]
32
44
  run-workflow = "virtool_workflow.cli:cli_main"
@@ -36,13 +48,14 @@ pytest = "^7.4.2"
36
48
  pytest-aiohttp = "^1.0.0"
37
49
  pytest-asyncio = "^0.21.0"
38
50
  pytest-mock = "^3.10.0"
39
- pre-commit = "^2.18.1"
40
51
  sphinx = "^4.0.2"
41
52
  syrupy = "^3.0.5"
42
53
  sphinx-autobuild = "^2021.3.14"
43
54
  sphinx-nameko-theme = "^0.0.3"
44
55
  pytest-structlog = "^0.6"
45
56
  sphinx-toolbox = "^3.5.0"
57
+ ruff = "^0.4.6"
58
+ piccolo-theme = "^0.22.0"
46
59
 
47
60
  [tool.pytest.ini_options]
48
61
  asyncio_mode = "auto"
@@ -58,8 +71,14 @@ exclude = [
58
71
  ".ruff_cache",
59
72
  "__pypackages__",
60
73
  ]
74
+ target-version = "py312"
61
75
 
62
76
  [tool.ruff.lint]
77
+ ignore = [
78
+ "ANN101",
79
+ "D203",
80
+ "D213"
81
+ ]
63
82
  select = ["ALL"]
64
83
 
65
84
  [build-system]
@@ -4,7 +4,11 @@ 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, JobsAPIError, JobsAPIServerError
7
+ from virtool_workflow.errors import (
8
+ JobAlreadyAcquiredError,
9
+ JobsAPIError,
10
+ JobsAPIServerError,
11
+ )
8
12
 
9
13
  logger = get_logger("api")
10
14
 
@@ -39,7 +43,7 @@ async def acquire_job_by_id(
39
43
 
40
44
  if resp.status == 400:
41
45
  if "already acquired" in await resp.text():
42
- raise JobAlreadyAcquired(await resp.json())
46
+ raise JobAlreadyAcquiredError(await resp.json())
43
47
 
44
48
  logger.critical(
45
49
  "unexpected api error during job acquisition",
@@ -10,10 +10,10 @@ from aiohttp import (
10
10
  from structlog import get_logger
11
11
 
12
12
  from virtool_workflow.errors import (
13
- JobsAPIBadRequest,
14
- JobsAPIConflict,
15
- JobsAPIForbidden,
16
- JobsAPINotFound,
13
+ JobsAPIBadRequestError,
14
+ JobsAPIConflictError,
15
+ JobsAPIForbiddenError,
16
+ JobsAPINotFoundError,
17
17
  JobsAPIServerError,
18
18
  )
19
19
 
@@ -80,10 +80,10 @@ async def raise_exception_by_status_code(resp: ClientResponse):
80
80
  :raise JobsAPIServerError: the response status code is 500
81
81
  """
82
82
  status_exception_map = {
83
- 400: JobsAPIBadRequest,
84
- 403: JobsAPIForbidden,
85
- 404: JobsAPINotFound,
86
- 409: JobsAPIConflict,
83
+ 400: JobsAPIBadRequestError,
84
+ 403: JobsAPIForbiddenError,
85
+ 404: JobsAPINotFoundError,
86
+ 409: JobsAPIConflictError,
87
87
  500: JobsAPIServerError,
88
88
  }
89
89
 
@@ -13,7 +13,7 @@ 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.errors import MissingJobArgument
16
+ from virtool_workflow.errors import MissingJobArgumentError
17
17
  from virtool_workflow.files import VirtoolFileFormat
18
18
 
19
19
  logger = get_logger("api")
@@ -43,9 +43,7 @@ class WFIndex:
43
43
 
44
44
  @property
45
45
  def bowtie_path(self) -> Path:
46
- """The path to the Bowtie2 index prefix for the Virtool index.
47
-
48
- """
46
+ """The path to the Bowtie2 index prefix for the Virtool index."""
49
47
  return self.path / "reference"
50
48
 
51
49
  @property
@@ -151,7 +149,10 @@ class WFNewIndex:
151
149
  await self._api.patch_json(f"/indexes/{self.id}", {})
152
150
 
153
151
  async def upload(
154
- self, path: Path, fmt: VirtoolFileFormat = "fasta", name: str | None = None,
152
+ self,
153
+ path: Path,
154
+ fmt: VirtoolFileFormat = "fasta",
155
+ name: str | None = None,
155
156
  ):
156
157
  """Upload a file to associate with the index being built.
157
158
 
@@ -272,14 +273,16 @@ async def index(
272
273
 
273
274
  @fixture
274
275
  async def new_index(
275
- _api: APIClient, job: Job, proc: int, work_path: Path,
276
+ _api: APIClient,
277
+ job: Job,
278
+ proc: int,
279
+ work_path: Path,
276
280
  ) -> WFNewIndex:
277
- """The :class:`.WFNewIndex` for an index being created by the current job.
278
- """
281
+ """The :class:`.WFNewIndex` for an index being created by the current job."""
279
282
  try:
280
283
  id_ = job.args["index_id"]
281
284
  except KeyError:
282
- raise MissingJobArgument("Missing jobs args key 'index_id'")
285
+ raise MissingJobArgumentError("Missing jobs args key 'index_id'")
283
286
 
284
287
  log = logger.bind(resource="new_index", id=id_, job_id=job.id)
285
288
  log.info("loading index")
@@ -12,7 +12,7 @@ from virtool_core.models.samples import Quality, Sample
12
12
  from virtool_workflow.analysis.utils import ReadPaths
13
13
  from virtool_workflow.api.client import APIClient
14
14
  from virtool_workflow.data.uploads import WFUploads
15
- from virtool_workflow.errors import JobsAPINotFound
15
+ from virtool_workflow.errors import JobsAPINotFoundError
16
16
  from virtool_workflow.files import VirtoolFileFormat
17
17
 
18
18
  logger = get_logger("api")
@@ -83,7 +83,10 @@ class WFNewSample:
83
83
 
84
84
  @fixture
85
85
  async def sample(
86
- _api: APIClient, job: Job, uploads: WFUploads, work_path: Path,
86
+ _api: APIClient,
87
+ job: Job,
88
+ uploads: WFUploads,
89
+ work_path: Path,
87
90
  ) -> WFSample:
88
91
  """The sample associated with the current job."""
89
92
  id_ = job.args["sample_id"]
@@ -92,8 +95,8 @@ async def sample(
92
95
 
93
96
  try:
94
97
  sample_json = await _api.get_json(base_url_path)
95
- except JobsAPINotFound:
96
- raise JobsAPINotFound("Sample not found")
98
+ except JobsAPINotFoundError:
99
+ raise JobsAPINotFoundError("Sample not found")
97
100
 
98
101
  sample = Sample(**sample_json)
99
102
 
@@ -101,7 +104,8 @@ async def sample(
101
104
  await asyncio.to_thread(reads_path.mkdir, exist_ok=True, parents=True)
102
105
 
103
106
  await _api.get_file(
104
- f"{base_url_path}/reads/reads_1.fq.gz", reads_path / "reads_1.fq.gz",
107
+ f"{base_url_path}/reads/reads_1.fq.gz",
108
+ reads_path / "reads_1.fq.gz",
105
109
  )
106
110
 
107
111
  if sample.paired:
@@ -110,7 +114,8 @@ async def sample(
110
114
  reads_path / "reads_2.fq.gz",
111
115
  )
112
116
  await _api.get_file(
113
- f"{base_url_path}/reads/reads_2.fq.gz", reads_path / "reads_2.fq.gz",
117
+ f"{base_url_path}/reads/reads_2.fq.gz",
118
+ reads_path / "reads_2.fq.gz",
114
119
  )
115
120
  else:
116
121
  read_paths = (reads_path / "reads_1.fq.gz",)
@@ -127,7 +132,10 @@ async def sample(
127
132
 
128
133
  @fixture
129
134
  async def new_sample(
130
- _api: APIClient, job: Job, uploads: WFUploads, work_path: Path,
135
+ _api: APIClient,
136
+ job: Job,
137
+ uploads: WFUploads,
138
+ work_path: Path,
131
139
  ) -> WFNewSample:
132
140
  """The sample associated with the current job."""
133
141
  id_ = job.args["sample_id"]
@@ -15,7 +15,7 @@ from virtool_core.models.subtraction import (
15
15
  from virtool_workflow.api.client import APIClient
16
16
  from virtool_workflow.data.analyses import WFAnalysis
17
17
  from virtool_workflow.data.uploads import WFUploads
18
- from virtool_workflow.errors import MissingJobArgument
18
+ from virtool_workflow.errors import MissingJobArgumentError
19
19
 
20
20
  logger = get_logger("api")
21
21
 
@@ -118,7 +118,9 @@ class WFNewSubtraction:
118
118
 
119
119
  @fixture
120
120
  async def subtractions(
121
- _api: APIClient, analysis: WFAnalysis, work_path: Path,
121
+ _api: APIClient,
122
+ analysis: WFAnalysis,
123
+ work_path: Path,
122
124
  ) -> list[WFSubtraction]:
123
125
  """The subtractions to be used for the current analysis job."""
124
126
  subtraction_work_path = work_path / "subtractions"
@@ -158,7 +160,10 @@ async def subtractions(
158
160
 
159
161
  @fixture
160
162
  async def new_subtraction(
161
- _api: APIClient, job: Job, uploads: WFUploads, work_path: Path,
163
+ _api: APIClient,
164
+ job: Job,
165
+ uploads: WFUploads,
166
+ work_path: Path,
162
167
  ) -> WFNewSubtraction:
163
168
  """A new subtraction that will be created during the current job.
164
169
 
@@ -167,12 +172,12 @@ async def new_subtraction(
167
172
  try:
168
173
  id_ = job.args["subtraction_id"]
169
174
  except KeyError:
170
- raise MissingJobArgument("subtraction_id")
175
+ raise MissingJobArgumentError("subtraction_id")
171
176
 
172
177
  try:
173
178
  upload_id = job.args["files"][0]["id"]
174
179
  except KeyError:
175
- raise MissingJobArgument("files")
180
+ raise MissingJobArgumentError("files")
176
181
 
177
182
  subtraction_json = await _api.get_json(f"/subtractions/{id_}")
178
183
  subtraction_ = Subtraction(**subtraction_json)
@@ -221,7 +226,9 @@ async def new_subtraction(
221
226
  log.info("Uploading subtraction file")
222
227
 
223
228
  await _api.put_file(
224
- f"/subtractions/{subtraction_.id}/files/{filename}", path, "unknown",
229
+ f"/subtractions/{subtraction_.id}/files/{filename}",
230
+ path,
231
+ "unknown",
225
232
  )
226
233
 
227
234
  log.info("Finished uploading subtraction file")
@@ -1,11 +1,13 @@
1
1
  """Create Workflows by decorating module scope functions."""
2
+
3
+ from collections.abc import Callable
2
4
  from types import ModuleType
3
- from typing import Callable
4
5
 
6
+ from virtool_workflow.errors import WorkflowStepsError
5
7
  from virtool_workflow.workflow import Workflow
6
8
 
7
9
 
8
- def step(func: Callable = None, *, name: str | None = None) -> Callable:
10
+ def step(func: Callable | None = None, *, name: str | None = None) -> Callable:
9
11
  """Mark a function as a workflow step function.
10
12
 
11
13
  :param func: the workflow step function
@@ -16,7 +18,7 @@ def step(func: Callable = None, *, name: str | None = None) -> Callable:
16
18
  return lambda _f: step(_f, name=name)
17
19
 
18
20
  func.__workflow_marker__ = "step"
19
- func.__workflow_step_props__ = dict(name=name)
21
+ func.__workflow_step_props__ = {"name": name}
20
22
 
21
23
  return func
22
24
 
@@ -39,7 +41,7 @@ def collect(module: ModuleType) -> Workflow:
39
41
  if marked.__workflow_marker__ == "step":
40
42
  workflow.step(marked, **marked.__workflow_step_props__)
41
43
 
42
- if len(workflow.steps) == 0:
43
- raise ValueError(f"No workflow steps could be found in {module}")
44
+ if not workflow.steps:
45
+ raise WorkflowStepsError(str(module))
44
46
 
45
47
  return workflow
@@ -0,0 +1,62 @@
1
+ """Custom exceptions for ``virtool_workflow``."""
2
+
3
+ from subprocess import SubprocessError
4
+
5
+
6
+ class JobAlreadyAcquiredError(Exception):
7
+ """Raised when an attempt is made to reacquire a job."""
8
+
9
+ def __init__(self, job_id: str) -> None:
10
+ """Initialize the exception with a message containing the job ID."""
11
+ super().__init__(
12
+ f"Job {job_id} is has already been acquired.",
13
+ )
14
+
15
+
16
+ class JobsAPIError(Exception):
17
+ """A base exception for errors due to HTTP errors from the jobs API."""
18
+
19
+
20
+ class JobsAPIBadRequestError(JobsAPIError):
21
+ """A ``400 Bad Request`` response was received from the jobs API."""
22
+
23
+ status = 400
24
+
25
+
26
+ class JobsAPIForbiddenError(JobsAPIError):
27
+ """A ``403 Forbidden`` response was received from the jobs API."""
28
+
29
+ status = 403
30
+
31
+
32
+ class JobsAPINotFoundError(JobsAPIError):
33
+ """A ``404 Not Found`` response was received from the jobs API."""
34
+
35
+ status = 404
36
+
37
+
38
+ class JobsAPIConflictError(JobsAPIError):
39
+ """A ``409 Conflict`` response was received from the jobs API."""
40
+
41
+ status = 409
42
+
43
+
44
+ class JobsAPIServerError(JobsAPIError):
45
+ """A ``500 Internal Server Error`` response was received from the jobs API."""
46
+
47
+ status = 500
48
+
49
+
50
+ class MissingJobArgumentError(ValueError):
51
+ """The `job.args` dict is missing a required key for some funcionality."""
52
+
53
+
54
+ class WorkflowStepsError(Exception):
55
+ """Raised when no workflow steps are found in a module."""
56
+
57
+ def __init__(self, module: str) -> None:
58
+ super().__init__(f"No workflow steps could be found in {module}")
59
+
60
+
61
+ class SubprocessFailedError(SubprocessError):
62
+ """Subprocess exited with non-zero status during a workflow."""
@@ -0,0 +1,40 @@
1
+ """Dataclasses for describing files uploaded to the Virtool server."""
2
+
3
+ import datetime
4
+ from dataclasses import dataclass
5
+ from typing import Literal
6
+
7
+ VirtoolFileFormat = Literal[
8
+ "sam",
9
+ "bam",
10
+ "fasta",
11
+ "fastq",
12
+ "csv",
13
+ "tsv",
14
+ "json",
15
+ "unknown",
16
+ ]
17
+ """A literal type hint for the format of a :class:`.VirtoolFile`."""
18
+
19
+
20
+ @dataclass
21
+ class VirtoolFile:
22
+ """A description of a file uploaded to the Virtool server."""
23
+
24
+ id: int
25
+ """The unique ID for the file."""
26
+
27
+ name: str
28
+ """The name of the file."""
29
+
30
+ size: int
31
+ """The size of the file in bytes."""
32
+
33
+ format: VirtoolFileFormat
34
+ """The format of the file."""
35
+
36
+ name_on_disk: str | None = None
37
+ """The actual name of the file on disk."""
38
+
39
+ uploaded_at: datetime.datetime | None = None
40
+ """When the file was uploaded."""
@@ -1,5 +1,4 @@
1
- """Hooks provide a way to do things when events happen during the workflow lifecycle.
2
- """
1
+ """Hooks do things when events happen during the workflow lifecycle."""
3
2
 
4
3
  from virtool_workflow.runtime.hook import Hook
5
4
 
@@ -124,7 +123,7 @@ __all__ = [
124
123
  ]
125
124
 
126
125
 
127
- def cleanup_builtin_status_hooks():
126
+ def cleanup_builtin_status_hooks() -> None:
128
127
  """Clear callbacks for built-in status hooks.
129
128
 
130
129
  This prevents carryover of hooks between tests. Carryover won't be encountered in
@@ -116,7 +116,6 @@ def data(
116
116
  "test": True,
117
117
  }
118
118
  job.ping = JobPing(pinged_at=static_datetime)
119
- job.rights = {}
120
119
 
121
120
  """A finalized sample to be used for testing analyses."""
122
121
  sample = SampleFactory.build()
@@ -2,8 +2,8 @@ import asyncio
2
2
  from asyncio import CancelledError
3
3
  from typing import Callable
4
4
 
5
- from aioredis import Redis
6
5
  from structlog import get_logger
6
+ from virtool_core.redis import Redis
7
7
 
8
8
  logger = get_logger("redis")
9
9
 
@@ -11,10 +11,11 @@ CANCELLATION_CHANNEL = "channel:cancel"
11
11
 
12
12
 
13
13
  async def get_next_job_with_timeout(
14
- list_name: str, redis: Redis, timeout: int = None
14
+ list_name: str,
15
+ redis: Redis,
16
+ timeout: int | None = None,
15
17
  ) -> str:
16
- """
17
- Get the next job ID from a Redis list and raise a :class:``Timeout`` error if one
18
+ """Get the next job ID from a Redis list and raise a :class:``Timeout`` error if one
18
19
  is not found in ``timeout`` seconds.
19
20
 
20
21
  :param list_name: the name of the list to pop from
@@ -24,35 +25,39 @@ async def get_next_job_with_timeout(
24
25
 
25
26
  """
26
27
  logger.info(
27
- "Waiting for a job", timeout=f"{timeout if timeout else 'infinity'} seconds"
28
+ "Waiting for a job",
29
+ timeout=f"{timeout if timeout else 'infinity'} seconds",
28
30
  )
29
31
 
30
32
  return await asyncio.wait_for(get_next_job(list_name, redis), timeout)
31
33
 
32
34
 
33
35
  async def get_next_job(list_name: str, redis: Redis) -> str:
34
- """
35
- Get the next job ID from a Redis list.
36
+ """Get the next job ID from a Redis list.
36
37
 
37
38
  :param list_name: the name of the list to pop from
38
39
  :param redis: the Redis client
39
40
  :return: the next job ID
40
41
 
41
42
  """
42
- result = await redis.blpop(list_name)
43
-
44
- if result is not None:
45
- job_id = str(result[1], encoding="utf-8")
43
+ if (job_id := await redis.blpop(list_name)) is not None:
46
44
  logger.info("pulled job id from redis", id=job_id)
47
45
  return job_id
48
46
 
47
+ raise ValueError("Unexpected None from job id list")
48
+
49
49
 
50
50
  async def wait_for_cancellation(redis: Redis, job_id: str, func: Callable):
51
- (channel,) = await redis.subscribe(CANCELLATION_CHANNEL)
51
+ """Call a function ``func`` when a job matching ``job_id`` is cancelled.
52
52
 
53
+ :param redis: the Redis client
54
+ :param job_id: the job ID to watch for
55
+ :param func: the function to call when the job is cancelled
56
+
57
+ """
53
58
  try:
54
- async for cancelled_job_id in channel.iter():
55
- if cancelled_job_id.decode() == job_id:
59
+ async for cancelled_job_id in redis.subscribe(CANCELLATION_CHANNEL):
60
+ if cancelled_job_id == job_id:
56
61
  return func()
57
62
 
58
63
  except CancelledError:
@@ -6,12 +6,11 @@ from asyncio import CancelledError
6
6
  from pathlib import Path
7
7
  from typing import Callable
8
8
 
9
- import pkg_resources
10
9
  import structlog
11
10
  from pyfixtures import FixtureScope, runs_in_new_fixture_context
12
11
  from structlog import get_logger
13
12
  from virtool_core.models.job import JobState
14
- from virtool_core.redis import configure_redis
13
+ from virtool_core.redis import Redis
15
14
 
16
15
  from virtool_workflow.api.acquire import acquire_job_by_id
17
16
  from virtool_workflow.api.client import api_client
@@ -42,6 +41,7 @@ from virtool_workflow.runtime.redis import (
42
41
  wait_for_cancellation,
43
42
  )
44
43
  from virtool_workflow.runtime.sentry import configure_sentry
44
+ from virtool_workflow.utils import get_virtool_workflow_version
45
45
  from virtool_workflow.workflow import Workflow
46
46
 
47
47
  logger = get_logger("runtime")
@@ -229,7 +229,7 @@ async def start_runtime(
229
229
 
230
230
  logger.info(
231
231
  "found virtool-workflow",
232
- version=pkg_resources.get_distribution("virtool-workflow").version,
232
+ version=get_virtool_workflow_version(),
233
233
  )
234
234
 
235
235
  workflow = workflow_loader()
@@ -239,7 +239,7 @@ async def start_runtime(
239
239
 
240
240
  configure_sentry(sentry_dsn)
241
241
 
242
- async with configure_redis(redis_connection_string, timeout=15) as redis:
242
+ async with Redis(redis_connection_string) as redis:
243
243
  try:
244
244
  job_id = await get_next_job_with_timeout(redis_list_name, redis, timeout)
245
245
  except asyncio.TimeoutError:
@@ -277,7 +277,7 @@ async def start_runtime(
277
277
  events.cancelled.set()
278
278
  run_workflow_task.cancel()
279
279
 
280
- async with configure_redis(redis_connection_string) as redis:
280
+ async with Redis(redis_connection_string) as redis:
281
281
  cancellation_task = asyncio.create_task(
282
282
  wait_for_cancellation(redis, job_id, cancel_workflow),
283
283
  )
@@ -1,20 +1,21 @@
1
1
  """Code for running and managing subprocesses."""
2
+
2
3
  import asyncio
3
4
  from asyncio.subprocess import Process
4
5
  from pathlib import Path
5
- from typing import Awaitable, Callable, Coroutine, Protocol
6
+ from typing import Callable, Coroutine, Protocol
6
7
 
7
8
  from pyfixtures import fixture
8
9
  from structlog import get_logger
9
10
  from virtool_core.utils import timestamp
10
11
 
11
- from virtool_workflow.errors import SubprocessFailed
12
+ from virtool_workflow.errors import SubprocessFailedError
12
13
 
13
14
  logger = get_logger("subprocess")
14
15
 
15
16
 
16
17
  class LineOutputHandler(Protocol):
17
- async def __call__(self, line: str):
18
+ async def __call__(self, line: bytes):
18
19
  """Handle input from stdin, or stderr, line by line.
19
20
 
20
21
  :param line: A line of output from the stream.
@@ -45,7 +46,8 @@ class RunSubprocess(Protocol):
45
46
 
46
47
 
47
48
  async def watch_pipe(
48
- stream: asyncio.StreamReader, handler: Callable[[bytes], Awaitable[None]],
49
+ stream: asyncio.StreamReader,
50
+ handler: LineOutputHandler,
49
51
  ):
50
52
  """Watch the stdout or stderr stream and pass lines to the `handler` callback function.
51
53
 
@@ -62,6 +64,21 @@ async def watch_pipe(
62
64
  await handler(line)
63
65
 
64
66
 
67
+ def stderr_logger(line: bytes):
68
+ """Log a line of stderr output and try to decode it as UTF-8.
69
+
70
+ If the line is not decodable, log it as a string.
71
+
72
+ :param line: a line of stderr output
73
+ """
74
+ line = line.rstrip()
75
+
76
+ try:
77
+ logger.info("stderr", line=line.decode())
78
+ except UnicodeDecodeError:
79
+ logger.info("stderr", line=line)
80
+
81
+
65
82
  async def _run_subprocess(
66
83
  command: list[str],
67
84
  stdout_handler: LineOutputHandler | None = None,
@@ -75,12 +92,17 @@ async def _run_subprocess(
75
92
 
76
93
  stdout = asyncio.subprocess.PIPE if stdout_handler else asyncio.subprocess.DEVNULL
77
94
 
78
- async def _stderr_handler(line):
79
- logger.info("stderr", line=line.rstrip())
95
+ if stderr_handler:
80
96
 
81
- if stderr_handler:
97
+ async def _stderr_handler(line):
98
+ stderr_logger(line)
82
99
  await stderr_handler(line)
83
100
 
101
+ else:
102
+
103
+ async def _stderr_handler(line):
104
+ stderr_logger(line)
105
+
84
106
  process = await asyncio.create_subprocess_exec(
85
107
  *(str(arg) for arg in command),
86
108
  cwd=cwd,
@@ -106,9 +128,12 @@ async def _run_subprocess(
106
128
 
107
129
  process.terminate()
108
130
 
109
- # Have to do this to avoid Event loop closed error.
131
+ # Have to do this in Python 3.10 to avoid Event loop closed error.
110
132
  # https://github.com/python/cpython/issues/88050
111
- process._transport.close()
133
+ try:
134
+ process._transport.close()
135
+ except AttributeError:
136
+ pass
112
137
 
113
138
  await process.wait()
114
139
  logger.info("subprocess exited", code=process.returncode)
@@ -122,7 +147,7 @@ async def _run_subprocess(
122
147
  # Exit code 15 indicates that the process was terminated. This is expected
123
148
  # when the workflow fails for some other reason, hence not an exception
124
149
  if process.returncode not in [0, 15, -15]:
125
- raise SubprocessFailed(
150
+ raise SubprocessFailedError(
126
151
  f"{command[0]} failed with exit code {process.returncode}\n"
127
152
  f"arguments: {command}\n",
128
153
  )
@@ -1,16 +1,16 @@
1
1
  import logging
2
2
 
3
- import pkg_resources
4
3
  import sentry_sdk
5
4
  from sentry_sdk.integrations.logging import LoggingIntegration
6
5
  from structlog import get_logger
7
6
 
7
+ from virtool_workflow.utils import get_virtool_workflow_version
8
+
8
9
  logger = get_logger("runtime")
9
10
 
10
11
 
11
12
  def configure_sentry(dsn: str):
12
- """Initialize Sentry for log aggregation.
13
- """
13
+ """Initialize Sentry for log aggregation."""
14
14
  if dsn:
15
15
  logger.info("initializing sentry", dsn=f"{dsn[:15]}...")
16
16
 
@@ -21,7 +21,7 @@ def configure_sentry(dsn: str):
21
21
  event_level=logging.WARNING,
22
22
  ),
23
23
  ],
24
- release=pkg_resources.get_distribution("virtool-workflow").version,
24
+ release=get_virtool_workflow_version(),
25
25
  traces_sample_rate=0.2,
26
26
  )
27
27
  else:
@@ -2,6 +2,7 @@ import asyncio
2
2
  import tarfile
3
3
  from collections.abc import Callable
4
4
  from functools import wraps
5
+ from importlib import metadata
5
6
  from inspect import iscoroutinefunction
6
7
  from pathlib import Path
7
8
 
@@ -18,6 +19,14 @@ def coerce_to_coroutine_function(func: Callable):
18
19
  return _func
19
20
 
20
21
 
22
+ def get_virtool_workflow_version() -> str:
23
+ """Get the version of the installed virtool-workflow package."""
24
+ try:
25
+ return metadata.version("virtool-workflow")
26
+ except metadata.PackageNotFoundError:
27
+ return "0.0.0"
28
+
29
+
21
30
  async def make_directory(path: Path):
22
31
  await asyncio.to_thread(path.mkdir, exist_ok=True, parents=True)
23
32
 
@@ -1,119 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: virtool-workflow
3
- Version: 6.0.0a11
4
- Summary: A framework for developing bioinformatics workflows for Virtool.
5
- Home-page: https://github.com/virtool/virtool-workflow
6
- License: MIT
7
- Author: Ian Boyes
8
- Requires-Python: >=3.10,<3.11
9
- Classifier: License :: OSI Approved :: MIT License
10
- Classifier: Programming Language :: Python :: 3
11
- Classifier: Programming Language :: Python :: 3.10
12
- Classifier: Topic :: Software Development :: Libraries
13
- Requires-Dist: aiofiles (>=0.7.0,<0.8.0)
14
- Requires-Dist: aiohttp (>=3.8.1,<4.0.0)
15
- Requires-Dist: aioredis (==1.3.1)
16
- Requires-Dist: biopython (>=1.81,<2.0)
17
- Requires-Dist: click (>=8.1.7,<9.0.0)
18
- Requires-Dist: orjson (>=3.9.9,<4.0.0)
19
- Requires-Dist: pydantic-factories (>=1.17.3,<2.0.0)
20
- Requires-Dist: pyfixtures (>=1.0.0,<2.0.0)
21
- Requires-Dist: sentry-sdk (>=1.5.7,<2.0.0)
22
- Requires-Dist: virtool-core (>=11.0.0,<12.0.0)
23
- Project-URL: Repository, https://github.com/virtool/virtool-workflow
24
- Description-Content-Type: text/markdown
25
-
26
- # Virtool Workflow
27
-
28
- ![Tests](https://github.com/virtool/virtool-workflow/workflows/ci/badge.svg?branch=main)
29
- [![PyPI version](https://badge.fury.io/py/virtool-workflow.svg)](https://badge.fury.io/py/virtool-workflow)
30
-
31
- A framework for developing bioinformatic workflows in Python.
32
-
33
- ```python
34
- from virtool_workflow import step
35
-
36
-
37
- @step
38
- def step_function():
39
- ...
40
-
41
-
42
- @step
43
- def step_function_2():
44
- ...
45
- ```
46
-
47
- - [Documentation](https://workflow.virtool.ca)
48
- - [Website](https://www.virtool.ca/)
49
-
50
- ## Contributing
51
-
52
- ### Commits
53
-
54
- All commits must follow the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0) specification.
55
-
56
- These standardized commit messages are used to automatically publish releases using [`semantic-release`](https://semantic-release.gitbook.io/semantic-release)
57
- after commits are merged to `main` from successful PRs.
58
-
59
- **Example**
60
-
61
- ```text
62
- feat: add API support for assigning labels to existing samples
63
- ```
64
-
65
- Descriptive bodies and footers are required where necessary to describe the impact of the commit. Use bullets where appropriate.
66
-
67
- Additional Requirements
68
-
69
- 1. **Write in the imperative**. For example, _"fix bug"_, not _"fixed bug"_ or _"fixes bug"_.
70
- 2. **Don't refer to issues or code reviews**. For example, don't write something like this: _"make style changes requested in review"_.
71
- Instead, _"update styles to improve accessibility"_.
72
- 3. **Commits are not your personal journal**. For example, don't write something like this: _"got server running again"_
73
- or _"oops. fixed my code smell"_.
74
-
75
- From Tim Pope: [A Note About Git Commit Messages](https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)
76
-
77
- ### Tests
78
-
79
- [Pytest](https://docs.pytest.org/en/7.1.x/ "Pytest") is used to implement unit
80
- and integration tests.
81
-
82
- A pytest plugin,
83
- [pytest-docker-compose](https://github.com/pytest-docker-compose/pytest-docker-compose)
84
- handles starting and stopping any required external services for integration
85
- tests. [docker-compose](https://docs.docker.com/compose/) will need to be
86
- installed on your system for this to work. It might also be necessary to setup a
87
- `docker` user group on your system, so you can [use docker without
88
- sudo](https://linoxide.com/use-docker-without-sudo-ubuntu/).
89
-
90
- `virtool-workflow` depends on some external bioinformatics tools such as [Bowtie
91
- 2](http://bowtie-bio.sourceforge.net/bowtie2/index.shtml),
92
- [FastQC](https://www.bioinformatics.babraham.ac.uk/projects/fastqc/), and
93
- [Skewer](https://github.com/relipmoc/skewer). Installation of these tools can be
94
- somewhat involved, so it's best to run the test suite using `docker`. The
95
- [virtool/workflow-tools](https://github.com/virtool/workflow-tools) image
96
- provides a base with all of the external dependencies pre-installed.
97
-
98
- [./tests/docker-compose.yml](./tests/docker-compose.yml) will run the test suite
99
- inside a container based on
100
- [virtool/workflow-tools](https://github.com/virtool/workflow-tools) and mount
101
- the local docker socket so that `pytest`, running inside the container, can
102
- manage the other services required by the integration tests.
103
-
104
- To run the entire test suite:
105
-
106
- ```sh
107
- cd tests
108
- docker-compose up --exit-code-from pytest
109
- ```
110
-
111
- To run a subset of the tests, `tests/integration` only for example:
112
-
113
- ```sh
114
- cd tests
115
- TEST_PATH=tests/integration docker-compose up --exit-code-from pytest
116
- ```
117
-
118
- :warning: The `TEST_PATH` is a relative path from the repository root, not the `tests` directory.
119
-
@@ -1,93 +0,0 @@
1
- # Virtool Workflow
2
-
3
- ![Tests](https://github.com/virtool/virtool-workflow/workflows/ci/badge.svg?branch=main)
4
- [![PyPI version](https://badge.fury.io/py/virtool-workflow.svg)](https://badge.fury.io/py/virtool-workflow)
5
-
6
- A framework for developing bioinformatic workflows in Python.
7
-
8
- ```python
9
- from virtool_workflow import step
10
-
11
-
12
- @step
13
- def step_function():
14
- ...
15
-
16
-
17
- @step
18
- def step_function_2():
19
- ...
20
- ```
21
-
22
- - [Documentation](https://workflow.virtool.ca)
23
- - [Website](https://www.virtool.ca/)
24
-
25
- ## Contributing
26
-
27
- ### Commits
28
-
29
- All commits must follow the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0) specification.
30
-
31
- These standardized commit messages are used to automatically publish releases using [`semantic-release`](https://semantic-release.gitbook.io/semantic-release)
32
- after commits are merged to `main` from successful PRs.
33
-
34
- **Example**
35
-
36
- ```text
37
- feat: add API support for assigning labels to existing samples
38
- ```
39
-
40
- Descriptive bodies and footers are required where necessary to describe the impact of the commit. Use bullets where appropriate.
41
-
42
- Additional Requirements
43
-
44
- 1. **Write in the imperative**. For example, _"fix bug"_, not _"fixed bug"_ or _"fixes bug"_.
45
- 2. **Don't refer to issues or code reviews**. For example, don't write something like this: _"make style changes requested in review"_.
46
- Instead, _"update styles to improve accessibility"_.
47
- 3. **Commits are not your personal journal**. For example, don't write something like this: _"got server running again"_
48
- or _"oops. fixed my code smell"_.
49
-
50
- From Tim Pope: [A Note About Git Commit Messages](https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)
51
-
52
- ### Tests
53
-
54
- [Pytest](https://docs.pytest.org/en/7.1.x/ "Pytest") is used to implement unit
55
- and integration tests.
56
-
57
- A pytest plugin,
58
- [pytest-docker-compose](https://github.com/pytest-docker-compose/pytest-docker-compose)
59
- handles starting and stopping any required external services for integration
60
- tests. [docker-compose](https://docs.docker.com/compose/) will need to be
61
- installed on your system for this to work. It might also be necessary to setup a
62
- `docker` user group on your system, so you can [use docker without
63
- sudo](https://linoxide.com/use-docker-without-sudo-ubuntu/).
64
-
65
- `virtool-workflow` depends on some external bioinformatics tools such as [Bowtie
66
- 2](http://bowtie-bio.sourceforge.net/bowtie2/index.shtml),
67
- [FastQC](https://www.bioinformatics.babraham.ac.uk/projects/fastqc/), and
68
- [Skewer](https://github.com/relipmoc/skewer). Installation of these tools can be
69
- somewhat involved, so it's best to run the test suite using `docker`. The
70
- [virtool/workflow-tools](https://github.com/virtool/workflow-tools) image
71
- provides a base with all of the external dependencies pre-installed.
72
-
73
- [./tests/docker-compose.yml](./tests/docker-compose.yml) will run the test suite
74
- inside a container based on
75
- [virtool/workflow-tools](https://github.com/virtool/workflow-tools) and mount
76
- the local docker socket so that `pytest`, running inside the container, can
77
- manage the other services required by the integration tests.
78
-
79
- To run the entire test suite:
80
-
81
- ```sh
82
- cd tests
83
- docker-compose up --exit-code-from pytest
84
- ```
85
-
86
- To run a subset of the tests, `tests/integration` only for example:
87
-
88
- ```sh
89
- cd tests
90
- TEST_PATH=tests/integration docker-compose up --exit-code-from pytest
91
- ```
92
-
93
- :warning: The `TEST_PATH` is a relative path from the repository root, not the `tests` directory.
@@ -1,61 +0,0 @@
1
- from subprocess import SubprocessError
2
-
3
-
4
- class IllegalJobArguments(ValueError):
5
- """The `job.args` dict is in an illegal state."""
6
-
7
-
8
-
9
- class InsufficientJobRights(Exception):
10
- ...
11
-
12
-
13
- class JobAlreadyAcquired(Exception):
14
- def __init__(self, job_id: str):
15
- super(JobAlreadyAcquired, self).__init__(
16
- f"Job {job_id} is has already been acquired.",
17
- )
18
-
19
-
20
- class JobAlreadyFinalized(Exception):
21
- ...
22
-
23
-
24
- class JobsAPIError(Exception):
25
- """A base exception for errors due to HTTP errors from the jobs API."""
26
-
27
-
28
-
29
- class JobsAPIBadRequest(JobsAPIError):
30
- """A ``400 Bad Request`` response from the jobs API."""
31
-
32
- status = 400
33
-
34
-
35
- class JobsAPIForbidden(JobsAPIError):
36
- status = 403
37
-
38
-
39
- class JobsAPINotFound(JobsAPIError):
40
- status = 404
41
-
42
-
43
- class JobsAPIConflict(JobsAPIError):
44
- status = 409
45
-
46
-
47
- class JobsAPIServerError(JobsAPIError):
48
- status = 500
49
-
50
-
51
- class MissingJobArgument(ValueError):
52
- """The `job.args` dict is missing a required key for some funcionality."""
53
-
54
-
55
-
56
- class NotFound(KeyError):
57
- ...
58
-
59
-
60
- class SubprocessFailed(SubprocessError):
61
- """Subprocess exited with non-zero status during a workflow."""
@@ -1,24 +0,0 @@
1
- from dataclasses import dataclass
2
- from datetime import datetime
3
- from typing import Literal
4
-
5
- VirtoolFileFormat = Literal[
6
- "sam",
7
- "bam",
8
- "fasta",
9
- "fastq",
10
- "csv",
11
- "tsv",
12
- "json",
13
- "unknown",
14
- ]
15
-
16
-
17
- @dataclass
18
- class VirtoolFile:
19
- id: int
20
- name: str
21
- size: int
22
- format: VirtoolFileFormat
23
- name_on_disk: str = None
24
- uploaded_at: datetime = None