rcdb 0.9.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rcdb/__init__.py +128 -0
- rcdb/__main__.py +3 -0
- rcdb/admin_cmd.py +88 -0
- rcdb/alias.py +90 -0
- rcdb/app_context.py +100 -0
- rcdb/coda_parser.py +308 -0
- rcdb/condition_query_builder.py +34 -0
- rcdb/config_parser.py +99 -0
- rcdb/constants.py +4 -0
- rcdb/errors.py +30 -0
- rcdb/file_archiver.py +35 -0
- rcdb/halld_daq_config_parser.py +79 -0
- rcdb/lexer.py +409 -0
- rcdb/log_format.py +12 -0
- rcdb/model.py +517 -0
- rcdb/provider.py +1386 -0
- rcdb/rcdb_cli/__init__.py +0 -0
- rcdb/rcdb_cli/__main__.py +4 -0
- rcdb/rcdb_cli/app.py +169 -0
- rcdb/rcdb_cli/context.py +5 -0
- rcdb/rcdb_cli/db.py +90 -0
- rcdb/rcdb_cli/ls.py +27 -0
- rcdb/rcdb_cli/mkdb.py +27 -0
- rcdb/rcdb_cli/repair/__init__.py +11 -0
- rcdb/rcdb_cli/repair/evio_files.py +144 -0
- rcdb/stopwatch.py +44 -0
- rcdb/update.py +34 -0
- rcdb/version.py +2 -0
- rcdb-0.9.1.dist-info/METADATA +43 -0
- rcdb-0.9.1.dist-info/RECORD +33 -0
- rcdb-0.9.1.dist-info/WHEEL +5 -0
- rcdb-0.9.1.dist-info/entry_points.txt +2 -0
- rcdb-0.9.1.dist-info/top_level.txt +1 -0
rcdb/__init__.py
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
from .model import ConditionType
|
|
2
|
+
from .provider import RCDBProvider
|
|
3
|
+
from .provider import ConfigurationProvider
|
|
4
|
+
from .rcdb_cli.app import rcdb_cli as run_rcdb_cli
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# This thing separates cells in data blob
|
|
8
|
+
blob_delimiter = "|"
|
|
9
|
+
|
|
10
|
+
# if cell of data table is a string and the string already contains blob_delimiter
|
|
11
|
+
# we have to encode blob_delimiter to blob_delimiter_replace on data write and decode it bach on data read
|
|
12
|
+
blob_delimiter_replacement = "&delimiter;"
|
|
13
|
+
|
|
14
|
+
SQL_SCHEMA_VERSION = 2
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class UpdateReasons(object):
|
|
18
|
+
"""Class holds a default values of UpdateContext.reason field
|
|
19
|
+
Attributes:
|
|
20
|
+
START - means update goes after 'GO', beginning of the data taking
|
|
21
|
+
UPDATE - after run in started, RCDB is being update each minute. That is how it is done
|
|
22
|
+
END - after run is ended
|
|
23
|
+
"""
|
|
24
|
+
START = 'start'
|
|
25
|
+
UPDATE = 'update'
|
|
26
|
+
END = 'end'
|
|
27
|
+
UNKNOWN = ''
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# -------------------------------------------------
|
|
31
|
+
# class holding a context of the update operations
|
|
32
|
+
# -------------------------------------------------
|
|
33
|
+
class UpdateContext(object):
|
|
34
|
+
"""Updates context
|
|
35
|
+
|
|
36
|
+
Attributes:
|
|
37
|
+
self.db - RCDBProvider with connection
|
|
38
|
+
self.reason - one of UpdateReasons or empty string
|
|
39
|
+
self.run - Run object
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __init__(self, db, reason):
|
|
43
|
+
self.db = db
|
|
44
|
+
self.reason = reason # Context in which daq is called '', 'start', 'update', 'end'
|
|
45
|
+
self.run = None # Run object
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# -------------------------------------------------
|
|
49
|
+
# function Convert list to DB text representation
|
|
50
|
+
# -------------------------------------------------
|
|
51
|
+
def list_to_db_text(values):
|
|
52
|
+
"""
|
|
53
|
+
Converts list of values like pedestal,threshold,baseline_preset values
|
|
54
|
+
to a space separated string - that is how the values are stored in the DB
|
|
55
|
+
|
|
56
|
+
:param values: list of values
|
|
57
|
+
:type values: []
|
|
58
|
+
|
|
59
|
+
:return: string with values as it is stored in DB
|
|
60
|
+
:rtype: basestring
|
|
61
|
+
"""
|
|
62
|
+
return " ".join([str(value) for value in values])
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def make_threshold_preset(db, board, values):
|
|
66
|
+
"""
|
|
67
|
+
checks if values
|
|
68
|
+
:param values:
|
|
69
|
+
:return:
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
if isinstance(values, list):
|
|
73
|
+
text_values = list_to_db_text(values)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class DefaultConditions(object):
|
|
77
|
+
"""
|
|
78
|
+
Holds common names for conditions and can ensure database have them
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
EVENT_RATE = 'event_rate'
|
|
82
|
+
EVENT_COUNT = 'event_count'
|
|
83
|
+
RUN_TYPE = 'run_type'
|
|
84
|
+
RUN_CONFIG = 'run_config'
|
|
85
|
+
RUN_LENGTH = 'run_length'
|
|
86
|
+
RUN_START_TIME = 'run_start_time'
|
|
87
|
+
RUN_END_TIME = 'run_end_time'
|
|
88
|
+
DAQ_SETUP = 'daq_setup'
|
|
89
|
+
SESSION = 'session'
|
|
90
|
+
USER_COMMENT = 'user_comment'
|
|
91
|
+
COMPONENTS = 'components'
|
|
92
|
+
COMPONENT_STATS = 'component_stats'
|
|
93
|
+
RTVS = 'rtvs'
|
|
94
|
+
IS_VALID_RUN_END = 'is_valid_run_end'
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def create_default_condition_types(db):
|
|
98
|
+
"""
|
|
99
|
+
Checks if condition types listed in class exist in the database and create them if not
|
|
100
|
+
:param db: RCDBProvider connected to database
|
|
101
|
+
:type db: RCDBProvider
|
|
102
|
+
|
|
103
|
+
:return: None
|
|
104
|
+
"""
|
|
105
|
+
all_types_dict = {t.name: t for t in db.get_condition_types()}
|
|
106
|
+
|
|
107
|
+
def create_condition_type(name, value_type, description=""):
|
|
108
|
+
all_types_dict[name] if name in all_types_dict.keys() \
|
|
109
|
+
else db.create_condition_type(name, value_type, description)
|
|
110
|
+
|
|
111
|
+
# get or create condition type
|
|
112
|
+
create_condition_type(DefaultConditions.EVENT_RATE, ConditionType.FLOAT_FIELD, "Average event rate")
|
|
113
|
+
create_condition_type(DefaultConditions.EVENT_COUNT, ConditionType.INT_FIELD, "Number of events")
|
|
114
|
+
create_condition_type(DefaultConditions.RUN_TYPE, ConditionType.STRING_FIELD, "DAQ Run type")
|
|
115
|
+
create_condition_type(DefaultConditions.RUN_CONFIG, ConditionType.STRING_FIELD, "DAQ Run configuration")
|
|
116
|
+
create_condition_type(DefaultConditions.SESSION, ConditionType.STRING_FIELD)
|
|
117
|
+
create_condition_type(DefaultConditions.USER_COMMENT, ConditionType.STRING_FIELD)
|
|
118
|
+
create_condition_type(DefaultConditions.COMPONENTS, ConditionType.JSON_FIELD)
|
|
119
|
+
create_condition_type(DefaultConditions.RTVS, ConditionType.JSON_FIELD)
|
|
120
|
+
create_condition_type(DefaultConditions.COMPONENT_STATS, ConditionType.JSON_FIELD)
|
|
121
|
+
create_condition_type(DefaultConditions.IS_VALID_RUN_END, ConditionType.BOOL_FIELD,
|
|
122
|
+
"True if a run has valid run-end record. "
|
|
123
|
+
"False means the run was aborted/crashed at some point")
|
|
124
|
+
create_condition_type(DefaultConditions.RUN_LENGTH, ConditionType.INT_FIELD, "Length of the run ")
|
|
125
|
+
create_condition_type(DefaultConditions.RUN_START_TIME, ConditionType.TIME_FIELD, "Run start time ")
|
|
126
|
+
create_condition_type(DefaultConditions.RUN_END_TIME, ConditionType.TIME_FIELD, "Run end time (last CODA update)")
|
|
127
|
+
|
|
128
|
+
|
rcdb/__main__.py
ADDED
rcdb/admin_cmd.py
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import posixpath
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from rcdb.app_context import RcdbApplicationContext, parse_run_range
|
|
8
|
+
from rcdb import RCDBProvider
|
|
9
|
+
|
|
10
|
+
pass_rcdb_context = click.make_pass_decorator(RcdbApplicationContext)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_default_config_path():
|
|
14
|
+
return os.path.join(os.path.expanduser('~'), '.rcdb_user')
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@click.group()
|
|
18
|
+
@click.option('--user-config', envvar='RCDB_USER_CONFIG', default=get_default_config_path, metavar='PATH', help='Changes the user config location.')
|
|
19
|
+
@click.option('--connection', '-c', envvar='RCDB_CONNECTION', help='Database connection string', default=None, required=True)
|
|
20
|
+
@click.option('--config', nargs=2, multiple=True, metavar='KEY VALUE', help='Overrides a config key/value pair.')
|
|
21
|
+
@click.option('--verbose', '-v', is_flag=True, help='Enables verbose mode.')
|
|
22
|
+
@click.version_option('1.0')
|
|
23
|
+
@click.pass_context
|
|
24
|
+
def cli(ctx, user_config, connection, config, verbose):
|
|
25
|
+
"""'rcdb' is a RCDB (run conditions database) command line tool
|
|
26
|
+
|
|
27
|
+
This tool allows to select runs and get values as well as manage RCDB values
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
# Create a rcdb_app_context object and remember it as as the context object. From
|
|
31
|
+
# this point onwards other commands can refer to it by using the
|
|
32
|
+
# @pass_rcdb_context decorator.
|
|
33
|
+
ctx.obj = RcdbApplicationContext(os.path.abspath(user_config), connection)
|
|
34
|
+
ctx.obj.verbose = verbose
|
|
35
|
+
for key, value in config:
|
|
36
|
+
ctx.obj.set_config(key, value)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@cli.command(help="")
|
|
40
|
+
@click.argument('search', required=False)
|
|
41
|
+
@click.option('--long', '-l', 'is_long', is_flag=True, help='Prints condition full information')
|
|
42
|
+
@pass_rcdb_context
|
|
43
|
+
def walk(context, search, is_long):
|
|
44
|
+
"""Lists condition
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
"""
|
|
48
|
+
db = context.db
|
|
49
|
+
assert isinstance(db, RCDBProvider)
|
|
50
|
+
cnd_types = db.get_condition_types_by_name()
|
|
51
|
+
names = sorted(cnd_types.keys())
|
|
52
|
+
if search:
|
|
53
|
+
names = [n for n in names if search in n]
|
|
54
|
+
|
|
55
|
+
longest_len = len(max(names, key=len))
|
|
56
|
+
for name in names:
|
|
57
|
+
cnd_type = cnd_types[name]
|
|
58
|
+
click.echo("{0:<{1}} {2}".format(name, longest_len, cnd_type.description))
|
|
59
|
+
|
|
60
|
+
@cli.command()
|
|
61
|
+
@click.argument('search', required=False)
|
|
62
|
+
@click.option('--long', '-l', 'is_long', is_flag=True, help='Prints condition full information')
|
|
63
|
+
@pass_rcdb_context
|
|
64
|
+
def ls(context, search, is_long):
|
|
65
|
+
"""Lists condition
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
"""
|
|
69
|
+
db = context.db
|
|
70
|
+
assert isinstance(db, RCDBProvider)
|
|
71
|
+
cnd_types = db.get_condition_types_by_name()
|
|
72
|
+
names = sorted(cnd_types.keys())
|
|
73
|
+
if search:
|
|
74
|
+
names = [n for n in names if search in n]
|
|
75
|
+
|
|
76
|
+
longest_len = len(max(names, key=len))
|
|
77
|
+
for name in names:
|
|
78
|
+
cnd_type = cnd_types[name]
|
|
79
|
+
click.echo("{0:<{1}} {2}".format(name, longest_len, cnd_type.description))
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
if __name__ == '__main__':
|
|
88
|
+
cli(prog_name="rcdb")
|
rcdb/alias.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
class ConditionSearchAlias(object):
|
|
2
|
+
def __init__(self, name, expression, comment):
|
|
3
|
+
self.name = name
|
|
4
|
+
self.expression = expression
|
|
5
|
+
self.comment = comment
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
default_aliases = [
|
|
9
|
+
ConditionSearchAlias('is_production', """run_type in ['hd_all.tsg', 'hd_all.tsg_ps', 'hd_all.bcal_fcal_st.tsg'] and
|
|
10
|
+
beam_current and beam_current > 2 and
|
|
11
|
+
event_count > 500000 and
|
|
12
|
+
solenoid_current and solenoid_current > 100 and
|
|
13
|
+
collimator_diameter != 'Blocking'""",
|
|
14
|
+
"Is production run"),
|
|
15
|
+
|
|
16
|
+
ConditionSearchAlias('is_2018production', """daq_run == 'PHYSICS' and
|
|
17
|
+
beam_current > 2 and
|
|
18
|
+
event_count > 10000000 and
|
|
19
|
+
solenoid_current > 100 and
|
|
20
|
+
collimator_diameter != 'Blocking'""",
|
|
21
|
+
"Is production run"),
|
|
22
|
+
|
|
23
|
+
ConditionSearchAlias('is_primex_production', """daq_run == 'PHYSICS_PRIMEX' and
|
|
24
|
+
event_count > 1000000 and
|
|
25
|
+
collimator_diameter != 'Blocking'""",
|
|
26
|
+
"Is PrimEx production run"),
|
|
27
|
+
|
|
28
|
+
ConditionSearchAlias('is_dirc_production', """daq_run == 'PHYSICS_DIRC' and
|
|
29
|
+
beam_current > 2 and
|
|
30
|
+
event_count > 5000000 and
|
|
31
|
+
solenoid_current > 100 and
|
|
32
|
+
collimator_diameter != 'Blocking'""",
|
|
33
|
+
"Is DIRC production run"),
|
|
34
|
+
|
|
35
|
+
ConditionSearchAlias('is_src_production', """daq_run == 'PHYSICS_SRC' and
|
|
36
|
+
beam_current > 2 and
|
|
37
|
+
event_count > 5000000 and
|
|
38
|
+
solenoid_current > 100 and
|
|
39
|
+
collimator_diameter != 'Blocking'""",
|
|
40
|
+
"Is SRC production run"),
|
|
41
|
+
|
|
42
|
+
ConditionSearchAlias('is_cpp_production', """daq_run == 'PHYSICS_CPP' and
|
|
43
|
+
beam_current > 2 and
|
|
44
|
+
event_count > 5000000 and
|
|
45
|
+
solenoid_current > 100 and
|
|
46
|
+
collimator_diameter != 'Blocking'""",
|
|
47
|
+
"Is CPP production run"),
|
|
48
|
+
|
|
49
|
+
ConditionSearchAlias('is_production_long', """daq_run == 'PHYSICS_raw'
|
|
50
|
+
beam_current > 2 and
|
|
51
|
+
event_count > 5000000 and
|
|
52
|
+
solenoid_current > 100 and
|
|
53
|
+
collimator_diameter != 'Blocking'""",
|
|
54
|
+
"Is production run with long mode data"),
|
|
55
|
+
|
|
56
|
+
ConditionSearchAlias('is_cosmic', '"cosmic" in run_config and beam_current < 1 and event_count > 5000',
|
|
57
|
+
"Is cosmic run"),
|
|
58
|
+
|
|
59
|
+
ConditionSearchAlias('is_empty_target', "target_type == 'EMPTY & Ready'", "Target is empty"),
|
|
60
|
+
|
|
61
|
+
# These should be true starting in 2017. Need to check to make sure that 2016 data is accurate...
|
|
62
|
+
ConditionSearchAlias('is_amorph_radiator', "polarization_angle < 0.", "Amorphous Radiator"),
|
|
63
|
+
ConditionSearchAlias('is_coherent_beam', "polarization_angle >= 0.", "Coherent Beam"),
|
|
64
|
+
#ConditionSearchAlias('is_amorph_radiator', "radiator_index == -1 and radiator_type != 'None' and target_type == 'FULL & Ready'",
|
|
65
|
+
# "Amorphous Radiator"),
|
|
66
|
+
#ConditionSearchAlias('is_coherent_beam', "(radiator_id != 5 and radiator_id > 0) and target_type == 'FULL & Ready'", "Coherent Beam"),
|
|
67
|
+
|
|
68
|
+
ConditionSearchAlias('is_field_off', "solenoid_current < 100", " Field Off"),
|
|
69
|
+
|
|
70
|
+
ConditionSearchAlias('is_field_on', "solenoid_current >= 100", " Field On"),
|
|
71
|
+
|
|
72
|
+
ConditionSearchAlias('status_calibration', "status == 3", "Run status = calibration"),
|
|
73
|
+
|
|
74
|
+
ConditionSearchAlias('status_approved_long', "status == 2", "Run status = approved (long)"),
|
|
75
|
+
|
|
76
|
+
ConditionSearchAlias('status_approved', "status == 1", "Run status = approved"),
|
|
77
|
+
|
|
78
|
+
ConditionSearchAlias('status_unchecked', "status == -1", "Run status = unchecked"),
|
|
79
|
+
|
|
80
|
+
ConditionSearchAlias('status_reject', " status == 0", "Run status = reject"),
|
|
81
|
+
]
|
|
82
|
+
|
|
83
|
+
_def_al_by_name = None
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def get_default_aliases_by_name():
|
|
87
|
+
global _def_al_by_name
|
|
88
|
+
if _def_al_by_name is None:
|
|
89
|
+
_def_al_by_name = {al.name:al for al in default_aliases }
|
|
90
|
+
return _def_al_by_name
|
rcdb/app_context.py
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import click
|
|
2
|
+
from rcdb import RCDBProvider
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class RcdbApplicationContext(object):
|
|
7
|
+
|
|
8
|
+
def __init__(self, home, connection_str):
|
|
9
|
+
self.home = home
|
|
10
|
+
self._db_instance = None
|
|
11
|
+
|
|
12
|
+
self.config = {}
|
|
13
|
+
self.verbose = False
|
|
14
|
+
self.connection_str = connection_str
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def db(self):
|
|
18
|
+
if not self._db_instance:
|
|
19
|
+
self._db_instance = RCDBProvider(self.connection_str)
|
|
20
|
+
return self._db_instance
|
|
21
|
+
|
|
22
|
+
def set_config(self, key, value):
|
|
23
|
+
self.config[key] = value
|
|
24
|
+
if self.verbose:
|
|
25
|
+
click.echo(' config[%s] = %s' % (key, value), file=sys.stderr)
|
|
26
|
+
|
|
27
|
+
def __repr__(self):
|
|
28
|
+
return '<Repo %r>' % self.home
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class RcdbAdminApplicationContext(RcdbApplicationContext):
|
|
32
|
+
|
|
33
|
+
def __init__(self, home, connection_str):
|
|
34
|
+
super(RcdbAdminApplicationContext).__init__(home, connection_str)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def parse_run_range(run_range_str, run_periods=None):
|
|
38
|
+
""" Parses run range, returning a pair (run_from, run_to) or (run_from, None) or (None, None)
|
|
39
|
+
|
|
40
|
+
Function doesn't raise FormatError
|
|
41
|
+
:exception ValueError: if run_range_str is not str
|
|
42
|
+
|
|
43
|
+
:param run_range_str: string to parse
|
|
44
|
+
:return: (run_from, run_to). Function always return lower run number as run_from
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
if run_range_str is None:
|
|
48
|
+
return None, None
|
|
49
|
+
|
|
50
|
+
run_range_str = str(run_range_str).strip()
|
|
51
|
+
if not run_range_str:
|
|
52
|
+
return None, None
|
|
53
|
+
|
|
54
|
+
assert isinstance(run_range_str, str)
|
|
55
|
+
|
|
56
|
+
# Is it run period?
|
|
57
|
+
if run_periods:
|
|
58
|
+
if run_range_str in run_periods:
|
|
59
|
+
run_min, run_max, descr = run_periods[run_range_str]
|
|
60
|
+
return run_min, run_max
|
|
61
|
+
|
|
62
|
+
# Have run-range?
|
|
63
|
+
if '-' in run_range_str:
|
|
64
|
+
tokens = [t.strip() for t in run_range_str.split("-")]
|
|
65
|
+
try:
|
|
66
|
+
run_from = int(tokens[0])
|
|
67
|
+
except (ValueError, KeyError):
|
|
68
|
+
return None, None
|
|
69
|
+
|
|
70
|
+
try:
|
|
71
|
+
run_to = int(tokens[1])
|
|
72
|
+
except (ValueError, KeyError):
|
|
73
|
+
return run_from, None
|
|
74
|
+
|
|
75
|
+
return (run_from, run_to) if run_from <= run_to else (run_to, run_from)
|
|
76
|
+
|
|
77
|
+
# Have run number?
|
|
78
|
+
if run_range_str.isdigit():
|
|
79
|
+
return int(run_range_str), None
|
|
80
|
+
|
|
81
|
+
# Default return is index
|
|
82
|
+
return None, None
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def minmax_run_range(run_range: tuple):
|
|
86
|
+
"""If one or both values of run_range tuple is None, replaces it to 0 or sys.maxsize
|
|
87
|
+
|
|
88
|
+
> minmax_run_range((None, None))
|
|
89
|
+
(0, sys.maxsize)
|
|
90
|
+
> minmax_run_range((None, value2))
|
|
91
|
+
(0, value2)
|
|
92
|
+
> minmax_run_range((value1, None))
|
|
93
|
+
(value1, sys.maxsize)
|
|
94
|
+
> minmax_run_range((value1, value2))
|
|
95
|
+
(value1, value2)
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
min_run = 0 if run_range[0] is None else run_range[0]
|
|
99
|
+
max_run = sys.maxsize if run_range[1] is None else run_range[1]
|
|
100
|
+
return min_run, max_run
|