looper 1.6.0a2__tar.gz → 1.7.0__tar.gz
Sign up to get free protection for your applications and to get access to all the features.
- {looper-1.6.0a2/looper.egg-info → looper-1.7.0}/PKG-INFO +3 -3
- looper-1.7.0/looper/_version.py +1 -0
- {looper-1.6.0a2 → looper-1.7.0}/looper/cli_looper.py +24 -4
- {looper-1.6.0a2 → looper-1.7.0}/looper/conductor.py +14 -0
- {looper-1.6.0a2 → looper-1.7.0}/looper/looper.py +11 -4
- {looper-1.6.0a2 → looper-1.7.0}/looper/project.py +3 -5
- {looper-1.6.0a2 → looper-1.7.0/looper.egg-info}/PKG-INFO +3 -3
- {looper-1.6.0a2 → looper-1.7.0}/looper.egg-info/requires.txt +2 -2
- {looper-1.6.0a2 → looper-1.7.0}/requirements/requirements-all.txt +2 -2
- {looper-1.6.0a2 → looper-1.7.0}/requirements/requirements-test.txt +1 -1
- looper-1.7.0/tests/test_natural_range.py +206 -0
- looper-1.6.0a2/looper/_version.py +0 -1
- looper-1.6.0a2/tests/test_natural_range.py +0 -196
- {looper-1.6.0a2 → looper-1.7.0}/LICENSE.txt +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/MANIFEST.in +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/README.md +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/logo_looper.svg +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/looper/__init__.py +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/looper/__main__.py +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/looper/cli_divvy.py +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/looper/const.py +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/looper/default_config/divvy_config.yaml +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/looper/default_config/divvy_templates/localhost_bulker_template.sub +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/looper/default_config/divvy_templates/localhost_docker_template.sub +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/looper/default_config/divvy_templates/localhost_singularity_template.sub +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/looper/default_config/divvy_templates/localhost_template.sub +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/looper/default_config/divvy_templates/lsf_template.sub +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/looper/default_config/divvy_templates/sge_template.sub +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/looper/default_config/divvy_templates/slurm_singularity_template.sub +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/looper/default_config/divvy_templates/slurm_template.sub +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/looper/divvy.py +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/looper/exceptions.py +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/looper/jinja_templates/footer.html +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/looper/jinja_templates/footer_index.html +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/looper/jinja_templates/head.html +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/looper/jinja_templates/index.html +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/looper/jinja_templates/logo.html +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/looper/jinja_templates/navbar.html +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/looper/jinja_templates/navbar_links.html +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/looper/jinja_templates/navbar_list_parent.html +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/looper/jinja_templates/object.html +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/looper/jinja_templates/project_object.html +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/looper/jinja_templates/sample.html +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/looper/jinja_templates/status.html +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/looper/jinja_templates/status_table.html +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/looper/jinja_templates/status_table_no_links.html +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/looper/jinja_templates_old/footer.html +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/looper/jinja_templates_old/footer_index.html +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/looper/jinja_templates_old/head.html +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/looper/jinja_templates_old/index.html +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/looper/jinja_templates_old/logo.html +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/looper/jinja_templates_old/navbar.html +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/looper/jinja_templates_old/navbar_links.html +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/looper/jinja_templates_old/navbar_list_parent.html +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/looper/jinja_templates_old/object.html +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/looper/jinja_templates_old/project_object.html +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/looper/jinja_templates_old/sample.html +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/looper/jinja_templates_old/status.html +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/looper/jinja_templates_old/status_table.html +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/looper/jinja_templates_old/status_table_no_links.html +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/looper/parser_types.py +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/looper/pipeline_interface.py +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/looper/plugins.py +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/looper/processed_project.py +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/looper/schemas/divvy_config_schema.yaml +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/looper/schemas/pipeline_interface_schema_generic.yaml +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/looper/schemas/pipeline_interface_schema_project.yaml +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/looper/schemas/pipeline_interface_schema_sample.yaml +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/looper/utils.py +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/looper.egg-info/SOURCES.txt +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/looper.egg-info/dependency_links.txt +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/looper.egg-info/entry_points.txt +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/looper.egg-info/top_level.txt +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/requirements/requirements-doc.txt +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/setup.cfg +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/setup.py +0 -0
- {looper-1.6.0a2 → looper-1.7.0}/tests/test_clean.py +0 -0
- {looper-1.6.0a2 → 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(
|
@@ -495,6 +503,18 @@ def build_parser():
|
|
495
503
|
help="Number of attributes to display",
|
496
504
|
type=int,
|
497
505
|
)
|
506
|
+
parser.add_argument(
|
507
|
+
"--commands",
|
508
|
+
action="version",
|
509
|
+
version="{}".format(" ".join(subparsers.choices.keys())),
|
510
|
+
)
|
511
|
+
|
512
|
+
report_subparser.add_argument(
|
513
|
+
"--portable",
|
514
|
+
help="Makes html report portable.",
|
515
|
+
action="store_true",
|
516
|
+
)
|
517
|
+
|
498
518
|
result.append(parser)
|
499
519
|
return result
|
500
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
|
|
@@ -111,9 +111,7 @@ class Project(peppyProject):
|
|
111
111
|
compute settings.
|
112
112
|
"""
|
113
113
|
|
114
|
-
def __init__(
|
115
|
-
self, cfg=None, amendments=None, divcfg_path=None, runp=False, **kwargs
|
116
|
-
):
|
114
|
+
def __init__(self, cfg=None, amendments=None, divcfg_path=None, **kwargs):
|
117
115
|
super(Project, self).__init__(cfg=cfg, amendments=amendments)
|
118
116
|
prj_dict = kwargs.get("project_dict")
|
119
117
|
pep_config = kwargs.get("pep_config", None)
|
@@ -122,7 +120,7 @@ class Project(peppyProject):
|
|
122
120
|
|
123
121
|
# init project from pephub pep_config:
|
124
122
|
if prj_dict is not None and cfg is None:
|
125
|
-
self.
|
123
|
+
self._from_dict(prj_dict)
|
126
124
|
self["_config_file"] = os.getcwd() # for finding pipeline interface
|
127
125
|
self["pep_config"] = pep_config
|
128
126
|
|
@@ -916,7 +914,7 @@ def make_set(items):
|
|
916
914
|
try:
|
917
915
|
# Check if user input single integer value for inclusion/exclusion criteria
|
918
916
|
if len(items) == 1:
|
919
|
-
items = list(map(
|
917
|
+
items = list(map(str, items)) # list(int(items[0]))
|
920
918
|
except:
|
921
919
|
if isinstance(items, str):
|
922
920
|
items = [items]
|
@@ -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.0a2"
|
@@ -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.0a2 → looper-1.7.0}/looper/default_config/divvy_templates/localhost_bulker_template.sub
RENAMED
File without changes
|
{looper-1.6.0a2 → looper-1.7.0}/looper/default_config/divvy_templates/localhost_docker_template.sub
RENAMED
File without changes
|
File without changes
|
{looper-1.6.0a2 → 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.0a2 → 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
|