looper 1.6.0a3__tar.gz → 1.7.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.
- {looper-1.6.0a3/looper.egg-info → looper-1.7.0}/PKG-INFO +3 -3
- looper-1.7.0/looper/_version.py +1 -0
- {looper-1.6.0a3 → looper-1.7.0}/looper/cli_looper.py +18 -4
- {looper-1.6.0a3 → looper-1.7.0}/looper/conductor.py +14 -0
- {looper-1.6.0a3 → looper-1.7.0}/looper/looper.py +11 -4
- {looper-1.6.0a3 → looper-1.7.0/looper.egg-info}/PKG-INFO +3 -3
- {looper-1.6.0a3 → looper-1.7.0}/looper.egg-info/requires.txt +2 -2
- {looper-1.6.0a3 → looper-1.7.0}/requirements/requirements-all.txt +2 -2
- {looper-1.6.0a3 → looper-1.7.0}/requirements/requirements-test.txt +1 -1
- looper-1.7.0/tests/test_natural_range.py +206 -0
- looper-1.6.0a3/looper/_version.py +0 -1
- looper-1.6.0a3/tests/test_natural_range.py +0 -196
- {looper-1.6.0a3 → looper-1.7.0}/LICENSE.txt +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/MANIFEST.in +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/README.md +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/logo_looper.svg +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/looper/__init__.py +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/looper/__main__.py +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/looper/cli_divvy.py +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/looper/const.py +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/looper/default_config/divvy_config.yaml +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/looper/default_config/divvy_templates/localhost_bulker_template.sub +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/looper/default_config/divvy_templates/localhost_docker_template.sub +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/looper/default_config/divvy_templates/localhost_singularity_template.sub +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/looper/default_config/divvy_templates/localhost_template.sub +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/looper/default_config/divvy_templates/lsf_template.sub +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/looper/default_config/divvy_templates/sge_template.sub +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/looper/default_config/divvy_templates/slurm_singularity_template.sub +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/looper/default_config/divvy_templates/slurm_template.sub +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/looper/divvy.py +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/looper/exceptions.py +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/looper/jinja_templates/footer.html +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/looper/jinja_templates/footer_index.html +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/looper/jinja_templates/head.html +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/looper/jinja_templates/index.html +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/looper/jinja_templates/logo.html +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/looper/jinja_templates/navbar.html +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/looper/jinja_templates/navbar_links.html +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/looper/jinja_templates/navbar_list_parent.html +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/looper/jinja_templates/object.html +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/looper/jinja_templates/project_object.html +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/looper/jinja_templates/sample.html +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/looper/jinja_templates/status.html +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/looper/jinja_templates/status_table.html +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/looper/jinja_templates/status_table_no_links.html +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/looper/jinja_templates_old/footer.html +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/looper/jinja_templates_old/footer_index.html +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/looper/jinja_templates_old/head.html +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/looper/jinja_templates_old/index.html +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/looper/jinja_templates_old/logo.html +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/looper/jinja_templates_old/navbar.html +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/looper/jinja_templates_old/navbar_links.html +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/looper/jinja_templates_old/navbar_list_parent.html +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/looper/jinja_templates_old/object.html +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/looper/jinja_templates_old/project_object.html +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/looper/jinja_templates_old/sample.html +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/looper/jinja_templates_old/status.html +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/looper/jinja_templates_old/status_table.html +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/looper/jinja_templates_old/status_table_no_links.html +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/looper/parser_types.py +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/looper/pipeline_interface.py +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/looper/plugins.py +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/looper/processed_project.py +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/looper/project.py +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/looper/schemas/divvy_config_schema.yaml +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/looper/schemas/pipeline_interface_schema_generic.yaml +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/looper/schemas/pipeline_interface_schema_project.yaml +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/looper/schemas/pipeline_interface_schema_sample.yaml +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/looper/utils.py +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/looper.egg-info/SOURCES.txt +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/looper.egg-info/dependency_links.txt +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/looper.egg-info/entry_points.txt +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/looper.egg-info/top_level.txt +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/requirements/requirements-doc.txt +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/setup.cfg +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/setup.py +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/tests/test_clean.py +0 -0
- {looper-1.6.0a3 → looper-1.7.0}/tests/test_desired_sample_range.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: looper
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.7.0
|
4
4
|
Summary: A pipeline submission engine that parses sample inputs and submits pipelines for each sample.
|
5
5
|
Home-page: https://github.com/pepkit/looper
|
6
6
|
Author: Nathan Sheffield, Vince Reuter, Michal Stolarczyk, Johanna Klughammer, Andre Rendeiro
|
@@ -22,8 +22,8 @@ Requires-Dist: jinja2
|
|
22
22
|
Requires-Dist: logmuse>=0.2.0
|
23
23
|
Requires-Dist: pandas>=2.0.2
|
24
24
|
Requires-Dist: pephubclient>=0.1.2
|
25
|
-
Requires-Dist: peppy>=0.40.0
|
26
|
-
Requires-Dist: pipestat>=0.
|
25
|
+
Requires-Dist: peppy>=0.40.0
|
26
|
+
Requires-Dist: pipestat>=0.8.0
|
27
27
|
Requires-Dist: pyyaml>=3.12
|
28
28
|
Requires-Dist: rich>=9.10.0
|
29
29
|
Requires-Dist: ubiquerg>=0.5.2
|
@@ -0,0 +1 @@
|
|
1
|
+
__version__ = "1.7.0"
|
@@ -219,19 +219,27 @@ def build_parser():
|
|
219
219
|
for subparser in [run_subparser, rerun_subparser]:
|
220
220
|
subparser.add_argument(
|
221
221
|
"-u",
|
222
|
-
"--lump",
|
222
|
+
"--lump-s",
|
223
223
|
default=None,
|
224
224
|
metavar="X",
|
225
225
|
type=html_range(min_val=0, max_val=100, step=0.1, value=0),
|
226
|
-
help="
|
226
|
+
help="Lump by size: total input file size (GB) to batch into one job",
|
227
227
|
)
|
228
228
|
subparser.add_argument(
|
229
229
|
"-n",
|
230
|
-
"--
|
230
|
+
"--lump-n",
|
231
231
|
default=None,
|
232
232
|
metavar="N",
|
233
233
|
type=html_range(min_val=1, max_val="num_samples", value=1),
|
234
|
-
help="
|
234
|
+
help="Lump by number: number of samples to batch into one job",
|
235
|
+
)
|
236
|
+
subparser.add_argument(
|
237
|
+
"-j",
|
238
|
+
"--lump-j",
|
239
|
+
default=None,
|
240
|
+
metavar="J",
|
241
|
+
type=int,
|
242
|
+
help="Lump samples into number of jobs.",
|
235
243
|
)
|
236
244
|
|
237
245
|
check_subparser.add_argument(
|
@@ -501,6 +509,12 @@ def build_parser():
|
|
501
509
|
version="{}".format(" ".join(subparsers.choices.keys())),
|
502
510
|
)
|
503
511
|
|
512
|
+
report_subparser.add_argument(
|
513
|
+
"--portable",
|
514
|
+
help="Makes html report portable.",
|
515
|
+
action="store_true",
|
516
|
+
)
|
517
|
+
|
504
518
|
result.append(parser)
|
505
519
|
return result
|
506
520
|
|
@@ -6,6 +6,7 @@ import os
|
|
6
6
|
import subprocess
|
7
7
|
import time
|
8
8
|
import yaml
|
9
|
+
from math import ceil
|
9
10
|
from copy import copy, deepcopy
|
10
11
|
from json import loads
|
11
12
|
from subprocess import check_output
|
@@ -132,6 +133,7 @@ class SubmissionConductor(object):
|
|
132
133
|
compute_variables=None,
|
133
134
|
max_cmds=None,
|
134
135
|
max_size=None,
|
136
|
+
max_jobs=None,
|
135
137
|
automatic=True,
|
136
138
|
collate=False,
|
137
139
|
):
|
@@ -166,6 +168,8 @@ class SubmissionConductor(object):
|
|
166
168
|
include in a single job script.
|
167
169
|
:param int | float | NoneType max_size: Upper bound on total file
|
168
170
|
size of inputs used by the commands lumped into single job script.
|
171
|
+
:param int | float | NoneType max_jobs: Upper bound on total number of jobs to
|
172
|
+
group samples for submission.
|
169
173
|
:param bool automatic: Whether the submission should be automatic once
|
170
174
|
the pool reaches capacity.
|
171
175
|
:param bool collate: Whether a collate job is to be submitted (runs on
|
@@ -200,6 +204,16 @@ class SubmissionConductor(object):
|
|
200
204
|
"{}".format(self.extra_pipe_args)
|
201
205
|
)
|
202
206
|
|
207
|
+
if max_jobs:
|
208
|
+
if max_jobs == 0 or max_jobs < 0:
|
209
|
+
raise ValueError(
|
210
|
+
"If specified, max job command count must be a positive integer, greater than zero."
|
211
|
+
)
|
212
|
+
|
213
|
+
num_samples = len(self.prj.samples)
|
214
|
+
samples_per_job = num_samples / max_jobs
|
215
|
+
max_cmds = ceil(samples_per_job)
|
216
|
+
|
203
217
|
if not self.collate:
|
204
218
|
self.automatic = automatic
|
205
219
|
if max_cmds is None and max_size is None:
|
@@ -404,8 +404,9 @@ class Runner(Executor):
|
|
404
404
|
extra_args=args.command_extra,
|
405
405
|
extra_args_override=args.command_extra_override,
|
406
406
|
ignore_flags=args.ignore_flags,
|
407
|
-
max_cmds=args.
|
408
|
-
max_size=args.
|
407
|
+
max_cmds=args.lump_n,
|
408
|
+
max_size=args.lump_s,
|
409
|
+
max_jobs=args.lump_j,
|
409
410
|
)
|
410
411
|
submission_conductors[piface.pipe_iface_file] = conductor
|
411
412
|
|
@@ -547,12 +548,16 @@ class Reporter(Executor):
|
|
547
548
|
p = self.prj
|
548
549
|
project_level = args.project
|
549
550
|
|
551
|
+
portable = args.portable
|
552
|
+
|
550
553
|
if project_level:
|
551
554
|
psms = self.prj.get_pipestat_managers(project_level=True)
|
552
555
|
print(psms)
|
553
556
|
for name, psm in psms.items():
|
554
557
|
# Summarize will generate the static HTML Report Function
|
555
|
-
report_directory = psm.summarize(
|
558
|
+
report_directory = psm.summarize(
|
559
|
+
looper_samples=self.prj.samples, portable=portable
|
560
|
+
)
|
556
561
|
print(f"Report directory: {report_directory}")
|
557
562
|
else:
|
558
563
|
for piface_source_samples in self.prj._samples_by_piface(
|
@@ -567,7 +572,9 @@ class Reporter(Executor):
|
|
567
572
|
print(psms)
|
568
573
|
for name, psm in psms.items():
|
569
574
|
# Summarize will generate the static HTML Report Function
|
570
|
-
report_directory = psm.summarize(
|
575
|
+
report_directory = psm.summarize(
|
576
|
+
looper_samples=self.prj.samples, portable=portable
|
577
|
+
)
|
571
578
|
print(f"Report directory: {report_directory}")
|
572
579
|
|
573
580
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: looper
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.7.0
|
4
4
|
Summary: A pipeline submission engine that parses sample inputs and submits pipelines for each sample.
|
5
5
|
Home-page: https://github.com/pepkit/looper
|
6
6
|
Author: Nathan Sheffield, Vince Reuter, Michal Stolarczyk, Johanna Klughammer, Andre Rendeiro
|
@@ -22,8 +22,8 @@ Requires-Dist: jinja2
|
|
22
22
|
Requires-Dist: logmuse>=0.2.0
|
23
23
|
Requires-Dist: pandas>=2.0.2
|
24
24
|
Requires-Dist: pephubclient>=0.1.2
|
25
|
-
Requires-Dist: peppy>=0.40.0
|
26
|
-
Requires-Dist: pipestat>=0.
|
25
|
+
Requires-Dist: peppy>=0.40.0
|
26
|
+
Requires-Dist: pipestat>=0.8.0
|
27
27
|
Requires-Dist: pyyaml>=3.12
|
28
28
|
Requires-Dist: rich>=9.10.0
|
29
29
|
Requires-Dist: ubiquerg>=0.5.2
|
@@ -0,0 +1,206 @@
|
|
1
|
+
"""Tests for the natural numbers range data type"""
|
2
|
+
|
3
|
+
from typing import *
|
4
|
+
import pytest
|
5
|
+
from hypothesis import given, strategies as st
|
6
|
+
from looper.utils import NatIntervalException, NatIntervalInclusive
|
7
|
+
|
8
|
+
|
9
|
+
gen_pos_int = st.integers(min_value=1)
|
10
|
+
gen_opt_int = st.one_of(st.integers(), st.none())
|
11
|
+
|
12
|
+
|
13
|
+
def is_non_pos(opt_int: Optional[int]) -> bool:
|
14
|
+
"""Determine whether the given value is non-positive (and non-null)."""
|
15
|
+
return opt_int is not None and opt_int < 1
|
16
|
+
|
17
|
+
|
18
|
+
def pytest_generate_tests(metafunc):
|
19
|
+
if "legit_delim" in metafunc.fixturenames:
|
20
|
+
metafunc.parametrize("legit_delim", [":", "-"])
|
21
|
+
|
22
|
+
|
23
|
+
def nondecreasing_pair_strategy(**kwargs):
|
24
|
+
"""Generate a pair of values in which first respects given upper bound and second is no more than first."""
|
25
|
+
return st.tuples(st.integers(**kwargs), st.integers(**kwargs)).filter(
|
26
|
+
lambda p: p[0] <= p[1]
|
27
|
+
)
|
28
|
+
|
29
|
+
|
30
|
+
class NaturalRangePureConstructorTests:
|
31
|
+
"""Tests for direct use of natural range primary constructor"""
|
32
|
+
|
33
|
+
@given(upper_bound=gen_pos_int)
|
34
|
+
def test_zero_is_prohibited(self, upper_bound):
|
35
|
+
"""Separate this case since it's an edge case."""
|
36
|
+
with pytest.raises(NatIntervalException):
|
37
|
+
NatIntervalInclusive(0, upper_bound)
|
38
|
+
|
39
|
+
@given(bounds=nondecreasing_pair_strategy(max_value=0))
|
40
|
+
def test_non_positive_is_prohibited(self, bounds):
|
41
|
+
lo, hi = bounds
|
42
|
+
with pytest.raises(NatIntervalException):
|
43
|
+
NatIntervalInclusive(lo, hi)
|
44
|
+
|
45
|
+
@given(bounds=st.tuples(st.integers(), st.integers()).filter(lambda p: p[0] > p[1]))
|
46
|
+
def test_upper_less_than_lower__fails_as_expected(self, bounds):
|
47
|
+
lo, hi = bounds
|
48
|
+
with pytest.raises(NatIntervalException):
|
49
|
+
NatIntervalInclusive(lo, hi)
|
50
|
+
|
51
|
+
|
52
|
+
class NaturalRangeFromStringTests:
|
53
|
+
"""Tests for parsing of natural number range from text, like CLI arg"""
|
54
|
+
|
55
|
+
|
56
|
+
@pytest.mark.parametrize(
|
57
|
+
"arg_template", ["0{sep}0", "{sep}0", "0{sep}", "0{sep}0", "{sep}0", "0{sep}"]
|
58
|
+
)
|
59
|
+
@given(upper_bound=gen_pos_int)
|
60
|
+
def test_from_string__zero__does_not_parse(arg_template, legit_delim, upper_bound):
|
61
|
+
arg = arg_template.format(sep=legit_delim)
|
62
|
+
with pytest.raises(NatIntervalException):
|
63
|
+
NatIntervalInclusive.from_string(arg, upper_bound=upper_bound)
|
64
|
+
|
65
|
+
|
66
|
+
@given(upper_bound=st.integers())
|
67
|
+
def test_from_string__just_delimiter__does_not_parse(legit_delim, upper_bound):
|
68
|
+
with pytest.raises(NatIntervalException):
|
69
|
+
NatIntervalInclusive.from_string(legit_delim, upper_bound=upper_bound)
|
70
|
+
|
71
|
+
|
72
|
+
@given(
|
73
|
+
lo_hi_upper=st.tuples(gen_opt_int, gen_opt_int, st.integers()).filter(
|
74
|
+
lambda t: (t[0] is not None or t[1] is not None)
|
75
|
+
and any(is_non_pos(n) for n in t)
|
76
|
+
)
|
77
|
+
)
|
78
|
+
def test_from_string__nonpositive_values__fail_with_expected_error(
|
79
|
+
lo_hi_upper, legit_delim
|
80
|
+
):
|
81
|
+
lo, hi, upper_bound = lo_hi_upper
|
82
|
+
if lo is None and hi is None:
|
83
|
+
raise ValueError("Both lower and upper bound generated are null.")
|
84
|
+
if lo is None:
|
85
|
+
arg = legit_delim + str(hi)
|
86
|
+
elif hi is None:
|
87
|
+
arg = str(lo) + legit_delim
|
88
|
+
else:
|
89
|
+
arg = str(lo) + legit_delim + str(hi)
|
90
|
+
with pytest.raises(NatIntervalException):
|
91
|
+
NatIntervalInclusive.from_string(arg, upper_bound=upper_bound)
|
92
|
+
|
93
|
+
|
94
|
+
@pytest.mark.parametrize("arg", ["1,2", "1;2", "1_2", "1/2", "1.2", "1~2"])
|
95
|
+
@given(upper_bound=st.integers(min_value=3))
|
96
|
+
def test_from_string__illegal_delimiter__fail_with_expected_error(arg, upper_bound):
|
97
|
+
with pytest.raises(NatIntervalException):
|
98
|
+
NatIntervalInclusive.from_string(arg, upper_bound=upper_bound)
|
99
|
+
|
100
|
+
|
101
|
+
@given(
|
102
|
+
lower_and_limit=st.tuples(st.integers(), st.integers()).filter(
|
103
|
+
lambda p: p[1] < p[0]
|
104
|
+
)
|
105
|
+
)
|
106
|
+
def test_from_string__one_sided_lower_with_samples_lt_bound__fails(
|
107
|
+
lower_and_limit, legit_delim
|
108
|
+
):
|
109
|
+
lower, limit = lower_and_limit
|
110
|
+
arg = str(lower) + legit_delim
|
111
|
+
with pytest.raises(NatIntervalException):
|
112
|
+
NatIntervalInclusive.from_string(arg, upper_bound=limit)
|
113
|
+
|
114
|
+
|
115
|
+
@given(lower_and_upper=nondecreasing_pair_strategy(min_value=1))
|
116
|
+
def test_from_string__one_sided_lower_with_samples_gteq_bound__succeeds(
|
117
|
+
lower_and_upper, legit_delim
|
118
|
+
):
|
119
|
+
lo, upper_bound = lower_and_upper
|
120
|
+
exp = NatIntervalInclusive(lo, upper_bound)
|
121
|
+
arg = str(lo) + legit_delim
|
122
|
+
obs = NatIntervalInclusive.from_string(arg, upper_bound=upper_bound)
|
123
|
+
assert obs == exp
|
124
|
+
|
125
|
+
|
126
|
+
@given(upper_and_limit=nondecreasing_pair_strategy(min_value=1))
|
127
|
+
def test_from_string__one_sided_upper_with_samples_gteq_bound__succeeds(
|
128
|
+
upper_and_limit, legit_delim
|
129
|
+
):
|
130
|
+
upper, limit = upper_and_limit
|
131
|
+
exp = NatIntervalInclusive(1, upper)
|
132
|
+
arg = legit_delim + str(upper)
|
133
|
+
obs = NatIntervalInclusive.from_string(arg, upper_bound=limit)
|
134
|
+
assert obs == exp
|
135
|
+
|
136
|
+
|
137
|
+
@given(
|
138
|
+
upper_and_limit=st.tuples(
|
139
|
+
st.integers(min_value=1), st.integers(min_value=1)
|
140
|
+
).filter(lambda p: p[1] < p[0])
|
141
|
+
)
|
142
|
+
def test_from_string__one_sided_upper_with_samples_lt_bound__uses_bound(
|
143
|
+
upper_and_limit, legit_delim
|
144
|
+
):
|
145
|
+
upper, limit = upper_and_limit
|
146
|
+
exp = NatIntervalInclusive(1, limit)
|
147
|
+
arg = legit_delim + str(upper)
|
148
|
+
obs = NatIntervalInclusive.from_string(arg, upper_bound=limit)
|
149
|
+
assert obs == exp
|
150
|
+
|
151
|
+
|
152
|
+
@given(
|
153
|
+
lower_upper_limit=st.tuples(gen_pos_int, gen_pos_int, gen_pos_int).filter(
|
154
|
+
lambda t: t[1] < t[0] or t[2] < t[0]
|
155
|
+
)
|
156
|
+
)
|
157
|
+
def test_from_string__two_sided_parse_upper_lt_lower(lower_upper_limit, legit_delim):
|
158
|
+
lo, hi, lim = lower_upper_limit
|
159
|
+
arg = str(lo) + legit_delim + str(hi)
|
160
|
+
with pytest.raises(NatIntervalException):
|
161
|
+
NatIntervalInclusive.from_string(arg, upper_bound=lim)
|
162
|
+
|
163
|
+
|
164
|
+
@given(
|
165
|
+
lo_hi_limit=st.tuples(st.integers(min_value=2), gen_pos_int, gen_pos_int).filter(
|
166
|
+
lambda t: t[2] < t[0] <= t[1]
|
167
|
+
)
|
168
|
+
)
|
169
|
+
def test_from_string__two_sided_parse_upper_gteq_lower_with_upper_limit_lt_lower(
|
170
|
+
lo_hi_limit, legit_delim
|
171
|
+
):
|
172
|
+
lo, hi, limit = lo_hi_limit
|
173
|
+
arg = str(lo) + legit_delim + str(hi)
|
174
|
+
with pytest.raises(NatIntervalException):
|
175
|
+
NatIntervalInclusive.from_string(arg, upper_bound=limit)
|
176
|
+
|
177
|
+
|
178
|
+
@given(
|
179
|
+
lo_hi_limit=st.tuples(gen_pos_int, gen_pos_int, gen_pos_int).filter(
|
180
|
+
lambda t: t[0] < t[2] < t[1]
|
181
|
+
)
|
182
|
+
)
|
183
|
+
def test_from_string__two_sided_parse_upper_gteq_lower_with_upper_limit_between_lower_and_upper(
|
184
|
+
lo_hi_limit,
|
185
|
+
legit_delim,
|
186
|
+
):
|
187
|
+
lo, hi, limit = lo_hi_limit
|
188
|
+
exp = NatIntervalInclusive(lo, limit)
|
189
|
+
arg = str(lo) + legit_delim + str(hi)
|
190
|
+
obs = NatIntervalInclusive.from_string(arg, upper_bound=limit)
|
191
|
+
assert obs == exp
|
192
|
+
|
193
|
+
|
194
|
+
@given(
|
195
|
+
lo_hi_upper=st.tuples(gen_pos_int, gen_pos_int, gen_pos_int).filter(
|
196
|
+
lambda t: t[0] <= t[1] <= t[2]
|
197
|
+
)
|
198
|
+
)
|
199
|
+
def test_from_string__two_sided_parse_upper_gteq_lower_with_upper_limit_gteq_upper(
|
200
|
+
lo_hi_upper, legit_delim
|
201
|
+
):
|
202
|
+
lo, hi, upper_bound = lo_hi_upper
|
203
|
+
exp = NatIntervalInclusive(lo, hi)
|
204
|
+
arg = f"{str(lo)}{legit_delim}{str(hi)}"
|
205
|
+
obs = NatIntervalInclusive.from_string(arg, upper_bound=upper_bound)
|
206
|
+
assert obs == exp
|
@@ -1 +0,0 @@
|
|
1
|
-
__version__ = "1.6.0a3"
|
@@ -1,196 +0,0 @@
|
|
1
|
-
"""Tests for the natural numbers range data type"""
|
2
|
-
|
3
|
-
from typing import *
|
4
|
-
import pytest
|
5
|
-
from hypothesis import Phase, given, settings, strategies as st
|
6
|
-
from looper.utils import NatIntervalException, NatIntervalInclusive
|
7
|
-
|
8
|
-
|
9
|
-
gen_pos_int = st.integers(min_value=1)
|
10
|
-
gen_opt_int = st.one_of(st.integers(), st.none())
|
11
|
-
|
12
|
-
|
13
|
-
def is_non_pos(opt_int: Optional[int]) -> bool:
|
14
|
-
"""Determine whether the given value is non-positive (and non-null)."""
|
15
|
-
return opt_int is not None and opt_int < 1
|
16
|
-
|
17
|
-
|
18
|
-
def pytest_generate_tests(metafunc):
|
19
|
-
if "legit_delim" in metafunc.fixturenames:
|
20
|
-
metafunc.parametrize("legit_delim", [":", "-"])
|
21
|
-
|
22
|
-
|
23
|
-
def nondecreasing_pair_strategy(**kwargs):
|
24
|
-
"""Generate a pair of values in which first respects given upper bound and second is no more than first."""
|
25
|
-
return st.tuples(st.integers(**kwargs), st.integers(**kwargs)).filter(
|
26
|
-
lambda p: p[0] <= p[1]
|
27
|
-
)
|
28
|
-
|
29
|
-
|
30
|
-
class NaturalRangePureConstructorTests:
|
31
|
-
"""Tests for direct use of natural range primary constructor"""
|
32
|
-
|
33
|
-
@given(upper_bound=gen_pos_int)
|
34
|
-
def test_zero_is_prohibited(self, upper_bound):
|
35
|
-
"""Separate this case since it's an edge case."""
|
36
|
-
with pytest.raises(NatIntervalException):
|
37
|
-
NatIntervalInclusive(0, upper_bound)
|
38
|
-
|
39
|
-
@given(bounds=nondecreasing_pair_strategy(max_value=0))
|
40
|
-
def test_non_positive_is_prohibited(self, bounds):
|
41
|
-
lo, hi = bounds
|
42
|
-
with pytest.raises(NatIntervalException):
|
43
|
-
NatIntervalInclusive(lo, hi)
|
44
|
-
|
45
|
-
@given(bounds=st.tuples(st.integers(), st.integers()).filter(lambda p: p[0] > p[1]))
|
46
|
-
def test_upper_less_than_lower__fails_as_expected(self, bounds):
|
47
|
-
lo, hi = bounds
|
48
|
-
with pytest.raises(NatIntervalException):
|
49
|
-
NatIntervalInclusive(lo, hi)
|
50
|
-
|
51
|
-
|
52
|
-
@pytest.mark.skip(reason="Unable to reproduce test failing locally.")
|
53
|
-
class NaturalRangeFromStringTests:
|
54
|
-
"""Tests for parsing of natural number range from text, like CLI arg"""
|
55
|
-
|
56
|
-
@pytest.mark.parametrize(
|
57
|
-
"arg_template", ["0{sep}0", "{sep}0", "0{sep}", "0{sep}0", "{sep}0", "0{sep}"]
|
58
|
-
)
|
59
|
-
@given(upper_bound=gen_pos_int)
|
60
|
-
def test_zero__does_not_parse(self, arg_template, legit_delim, upper_bound):
|
61
|
-
arg = arg_template.format(sep=legit_delim)
|
62
|
-
with pytest.raises(NatIntervalException):
|
63
|
-
NatIntervalInclusive.from_string(arg, upper_bound=upper_bound)
|
64
|
-
|
65
|
-
@given(upper_bound=st.integers())
|
66
|
-
def test_just_delimiter__does_not_parse(self, legit_delim, upper_bound):
|
67
|
-
with pytest.raises(NatIntervalException):
|
68
|
-
NatIntervalInclusive.from_string(legit_delim, upper_bound=upper_bound)
|
69
|
-
|
70
|
-
@given(
|
71
|
-
lo_hi_upper=st.tuples(gen_opt_int, gen_opt_int, st.integers()).filter(
|
72
|
-
lambda t: (t[0] is not None or t[1] is not None)
|
73
|
-
and any(is_non_pos(n) for n in t)
|
74
|
-
)
|
75
|
-
)
|
76
|
-
def test_nonpositive_values__fail_with_expected_error(
|
77
|
-
self, lo_hi_upper, legit_delim
|
78
|
-
):
|
79
|
-
lo, hi, upper_bound = lo_hi_upper
|
80
|
-
if lo is None and hi is None:
|
81
|
-
raise ValueError("Both lower and upper bound generated are null.")
|
82
|
-
if lo is None:
|
83
|
-
arg = legit_delim + str(hi)
|
84
|
-
elif hi is None:
|
85
|
-
arg = str(lo) + legit_delim
|
86
|
-
else:
|
87
|
-
arg = str(lo) + legit_delim + str(hi)
|
88
|
-
with pytest.raises(NatIntervalException):
|
89
|
-
NatIntervalInclusive.from_string(arg, upper_bound=upper_bound)
|
90
|
-
|
91
|
-
@pytest.mark.parametrize("arg", ["1,2", "1;2", "1_2", "1/2", "1.2", "1~2"])
|
92
|
-
@given(upper_bound=st.integers(min_value=3))
|
93
|
-
def test_illegal_delimiter__fail_with_expected_error(self, arg, upper_bound):
|
94
|
-
with pytest.raises(NatIntervalException):
|
95
|
-
NatIntervalInclusive.from_string(arg, upper_bound=upper_bound)
|
96
|
-
|
97
|
-
@given(
|
98
|
-
lower_and_limit=st.tuples(st.integers(), st.integers()).filter(
|
99
|
-
lambda p: p[1] < p[0]
|
100
|
-
)
|
101
|
-
)
|
102
|
-
def test_one_sided_lower_with_samples_lt_bound__fails(
|
103
|
-
self, lower_and_limit, legit_delim
|
104
|
-
):
|
105
|
-
lower, limit = lower_and_limit
|
106
|
-
arg = str(lower) + legit_delim
|
107
|
-
with pytest.raises(NatIntervalException):
|
108
|
-
NatIntervalInclusive.from_string(arg, upper_bound=limit)
|
109
|
-
|
110
|
-
@given(lower_and_upper=nondecreasing_pair_strategy(min_value=1))
|
111
|
-
def test_one_sided_lower_with_samples_gteq_bound__succeeds(
|
112
|
-
self, lower_and_upper, legit_delim
|
113
|
-
):
|
114
|
-
lo, upper_bound = lower_and_upper
|
115
|
-
exp = NatIntervalInclusive(lo, upper_bound)
|
116
|
-
arg = str(lo) + legit_delim
|
117
|
-
obs = NatIntervalInclusive.from_string(arg, upper_bound=upper_bound)
|
118
|
-
assert obs == exp
|
119
|
-
|
120
|
-
@given(upper_and_limit=nondecreasing_pair_strategy(min_value=1))
|
121
|
-
def test_one_sided_upper_with_samples_gteq_bound__succeeds(
|
122
|
-
self, upper_and_limit, legit_delim
|
123
|
-
):
|
124
|
-
upper, limit = upper_and_limit
|
125
|
-
exp = NatIntervalInclusive(1, upper)
|
126
|
-
arg = legit_delim + str(upper)
|
127
|
-
obs = NatIntervalInclusive.from_string(arg, upper_bound=limit)
|
128
|
-
assert obs == exp
|
129
|
-
|
130
|
-
@given(
|
131
|
-
upper_and_limit=st.tuples(
|
132
|
-
st.integers(min_value=1), st.integers(min_value=1)
|
133
|
-
).filter(lambda p: p[1] < p[0])
|
134
|
-
)
|
135
|
-
def test_one_sided_upper_with_samples_lt_bound__uses_bound(
|
136
|
-
self, upper_and_limit, legit_delim
|
137
|
-
):
|
138
|
-
upper, limit = upper_and_limit
|
139
|
-
exp = NatIntervalInclusive(1, limit)
|
140
|
-
arg = legit_delim + str(upper)
|
141
|
-
obs = NatIntervalInclusive.from_string(arg, upper_bound=limit)
|
142
|
-
assert obs == exp
|
143
|
-
|
144
|
-
@given(
|
145
|
-
lower_upper_limit=st.tuples(gen_pos_int, gen_pos_int, gen_pos_int).filter(
|
146
|
-
lambda t: t[1] < t[0] or t[2] < t[0]
|
147
|
-
)
|
148
|
-
)
|
149
|
-
def test_two_sided_parse_upper_lt_lower(self, lower_upper_limit, legit_delim):
|
150
|
-
lo, hi, lim = lower_upper_limit
|
151
|
-
arg = str(lo) + legit_delim + str(hi)
|
152
|
-
with pytest.raises(NatIntervalException):
|
153
|
-
NatIntervalInclusive.from_string(arg, upper_bound=lim)
|
154
|
-
|
155
|
-
@given(
|
156
|
-
lo_hi_limit=st.tuples(
|
157
|
-
st.integers(min_value=2), gen_pos_int, gen_pos_int
|
158
|
-
).filter(lambda t: t[2] < t[0] <= t[1])
|
159
|
-
)
|
160
|
-
def test_two_sided_parse_upper_gteq_lower_with_upper_limit_lt_lower(
|
161
|
-
self, lo_hi_limit, legit_delim
|
162
|
-
):
|
163
|
-
lo, hi, limit = lo_hi_limit
|
164
|
-
arg = str(lo) + legit_delim + str(hi)
|
165
|
-
with pytest.raises(NatIntervalException):
|
166
|
-
NatIntervalInclusive.from_string(arg, upper_bound=limit)
|
167
|
-
|
168
|
-
@given(
|
169
|
-
lo_hi_limit=st.tuples(gen_pos_int, gen_pos_int, gen_pos_int).filter(
|
170
|
-
lambda t: t[0] < t[2] < t[1]
|
171
|
-
)
|
172
|
-
)
|
173
|
-
def test_two_sided_parse_upper_gteq_lower_with_upper_limit_between_lower_and_upper(
|
174
|
-
self,
|
175
|
-
lo_hi_limit,
|
176
|
-
legit_delim,
|
177
|
-
):
|
178
|
-
lo, hi, limit = lo_hi_limit
|
179
|
-
exp = NatIntervalInclusive(lo, limit)
|
180
|
-
arg = str(lo) + legit_delim + str(hi)
|
181
|
-
obs = NatIntervalInclusive.from_string(arg, upper_bound=limit)
|
182
|
-
assert obs == exp
|
183
|
-
|
184
|
-
@given(
|
185
|
-
lo_hi_upper=st.tuples(gen_pos_int, gen_pos_int, gen_pos_int).filter(
|
186
|
-
lambda t: t[0] <= t[1] <= t[2]
|
187
|
-
)
|
188
|
-
)
|
189
|
-
def test_two_sided_parse_upper_gteq_lower_with_upper_limit_gteq_upper(
|
190
|
-
self, lo_hi_upper, legit_delim
|
191
|
-
):
|
192
|
-
lo, hi, upper_bound = lo_hi_upper
|
193
|
-
exp = NatIntervalInclusive(lo, hi)
|
194
|
-
arg = f"{str(lo)}{legit_delim}{str(hi)}"
|
195
|
-
obs = NatIntervalInclusive.from_string(arg, upper_bound=upper_bound)
|
196
|
-
assert obs == exp
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{looper-1.6.0a3 → looper-1.7.0}/looper/default_config/divvy_templates/localhost_bulker_template.sub
RENAMED
File without changes
|
{looper-1.6.0a3 → looper-1.7.0}/looper/default_config/divvy_templates/localhost_docker_template.sub
RENAMED
File without changes
|
File without changes
|
{looper-1.6.0a3 → looper-1.7.0}/looper/default_config/divvy_templates/localhost_template.sub
RENAMED
File without changes
|
File without changes
|
File without changes
|
{looper-1.6.0a3 → looper-1.7.0}/looper/default_config/divvy_templates/slurm_singularity_template.sub
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|