Skip to content

Commit 8dc16e7

Browse files
authored
Merge pull request #336 from pyiron/validate
Move validation routines to separate submodule
2 parents 3e29a93 + 947f53c commit 8dc16e7

File tree

4 files changed

+136
-151
lines changed

4 files changed

+136
-151
lines changed

pysqa/utils/basic.py

Lines changed: 5 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import getpass
55
import importlib
66
import os
7-
import re
87
from typing import List, Optional, Tuple, Union
98

109
import pandas
@@ -13,6 +12,7 @@
1312

1413
from pysqa.utils.execute import execute_command
1514
from pysqa.utils.queues import Queues
15+
from pysqa.utils.validate import value_error_if_none, value_in_range
1616

1717

1818
class BasisQueueAdapter(object):
@@ -372,15 +372,15 @@ def check_queue_parameters(
372372
"""
373373
if active_queue is None:
374374
active_queue = self._config["queues"][queue]
375-
cores = self._value_in_range(
375+
cores = value_in_range(
376376
value=cores,
377377
value_min=active_queue["cores_min"],
378378
value_max=active_queue["cores_max"],
379379
)
380-
run_time_max = self._value_in_range(
380+
run_time_max = value_in_range(
381381
value=run_time_max, value_max=active_queue["run_time_max"]
382382
)
383-
memory_max = self._value_in_range(
383+
memory_max = value_in_range(
384384
value=memory_max, value_max=active_queue["memory_max"]
385385
)
386386
return cores, run_time_max, memory_max
@@ -465,7 +465,7 @@ def _job_submission_template(
465465
"""
466466
if queue is None:
467467
queue = self._config["queue_primary"]
468-
self._value_error_if_none(value=command)
468+
value_error_if_none(value=command)
469469
if queue not in self.queue_list:
470470
raise ValueError(
471471
"The queue "
@@ -567,113 +567,3 @@ def _load_templates(queue_lst_dict: dict, directory: str = ".") -> None:
567567
+ error.message,
568568
lineno=error.lineno,
569569
)
570-
571-
@staticmethod
572-
def _value_error_if_none(value: str) -> None:
573-
"""
574-
Raise a ValueError if the value is None or not a string.
575-
576-
Args:
577-
value (str/None): The value to check.
578-
579-
Raises:
580-
ValueError: If the value is None.
581-
TypeError: If the value is not a string.
582-
"""
583-
if value is None:
584-
raise ValueError("Value cannot be None.")
585-
if not isinstance(value, str):
586-
raise TypeError()
587-
588-
@classmethod
589-
def _value_in_range(
590-
cls,
591-
value: Union[int, float, None],
592-
value_min: Union[int, float, None] = None,
593-
value_max: Union[int, float, None] = None,
594-
) -> Union[int, float, None]:
595-
"""
596-
Check if a value is within a specified range.
597-
598-
Args:
599-
value (int/float/None): The value to check.
600-
value_min (int/float/None): The minimum value. Defaults to None.
601-
value_max (int/float/None): The maximum value. Defaults to None.
602-
603-
Returns:
604-
int/float/None: The value if it is within the range, otherwise the minimum or maximum value.
605-
"""
606-
607-
if value is not None:
608-
value_, value_min_, value_max_ = [
609-
(
610-
cls._memory_spec_string_to_value(v)
611-
if v is not None and isinstance(v, str)
612-
else v
613-
)
614-
for v in (value, value_min, value_max)
615-
]
616-
# ATTENTION: '60000' is interpreted as '60000M' since default magnitude is 'M'
617-
# ATTENTION: int('60000') is interpreted as '60000B' since _memory_spec_string_to_value return the size in
618-
# ATTENTION: bytes, as target_magnitude = 'b'
619-
# We want to compare the the actual (k,m,g)byte value if there is any
620-
if value_min_ is not None and value_ < value_min_:
621-
return value_min
622-
if value_max_ is not None and value_ > value_max_:
623-
return value_max
624-
return value
625-
else:
626-
if value_min is not None:
627-
return value_min
628-
if value_max is not None:
629-
return value_max
630-
return value
631-
632-
@staticmethod
633-
def _is_memory_string(value: str) -> bool:
634-
"""
635-
Check if a string specifies a certain amount of memory.
636-
637-
Args:
638-
value (str): The string to check.
639-
640-
Returns:
641-
bool: True if the string matches a memory specification, False otherwise.
642-
"""
643-
memory_spec_pattern = r"[0-9]+[bBkKmMgGtT]?"
644-
return re.findall(memory_spec_pattern, value)[0] == value
645-
646-
@classmethod
647-
def _memory_spec_string_to_value(
648-
cls, value: str, default_magnitude: str = "m", target_magnitude: str = "b"
649-
) -> Union[int, float]:
650-
"""
651-
Converts a valid memory string (tested by _is_memory_string) into an integer/float value of desired
652-
magnitude `default_magnitude`. If it is a plain integer string (e.g.: '50000') it will be interpreted with
653-
the magnitude passed in by the `default_magnitude`. The output will rescaled to `target_magnitude`
654-
655-
Args:
656-
value (str): The string to convert.
657-
default_magnitude (str): The magnitude for interpreting plain integer strings [b, B, k, K, m, M, g, G, t, T]. Defaults to "m".
658-
target_magnitude (str): The magnitude to which the output value should be converted [b, B, k, K, m, M, g, G, t, T]. Defaults to "b".
659-
660-
Returns:
661-
Union[int, float]: The value of the string in `target_magnitude` units.
662-
"""
663-
magnitude_mapping = {"b": 0, "k": 1, "m": 2, "g": 3, "t": 4}
664-
if cls._is_memory_string(value):
665-
integer_pattern = r"[0-9]+"
666-
magnitude_pattern = r"[bBkKmMgGtT]+"
667-
integer_value = int(re.findall(integer_pattern, value)[0])
668-
669-
magnitude = re.findall(magnitude_pattern, value)
670-
if len(magnitude) > 0:
671-
magnitude = magnitude[0].lower()
672-
else:
673-
magnitude = default_magnitude.lower()
674-
# Convert it to default magnitude = megabytes
675-
return (integer_value * 1024 ** magnitude_mapping[magnitude]) / (
676-
1024 ** magnitude_mapping[target_magnitude]
677-
)
678-
else:
679-
return value

pysqa/utils/validate.py

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import re
2+
from typing import Union
3+
4+
5+
def value_error_if_none(value: str) -> None:
6+
"""
7+
Raise a ValueError if the value is None or not a string.
8+
9+
Args:
10+
value (str/None): The value to check.
11+
12+
Raises:
13+
ValueError: If the value is None.
14+
TypeError: If the value is not a string.
15+
"""
16+
if value is None:
17+
raise ValueError("Value cannot be None.")
18+
if not isinstance(value, str):
19+
raise TypeError()
20+
21+
22+
def value_in_range(
23+
value: Union[int, float, None],
24+
value_min: Union[int, float, None] = None,
25+
value_max: Union[int, float, None] = None,
26+
) -> Union[int, float, None]:
27+
"""
28+
Check if a value is within a specified range.
29+
30+
Args:
31+
value (int/float/None): The value to check.
32+
value_min (int/float/None): The minimum value. Defaults to None.
33+
value_max (int/float/None): The maximum value. Defaults to None.
34+
35+
Returns:
36+
int/float/None: The value if it is within the range, otherwise the minimum or maximum value.
37+
"""
38+
39+
if value is not None:
40+
value_, value_min_, value_max_ = [
41+
(
42+
_memory_spec_string_to_value(v)
43+
if v is not None and isinstance(v, str)
44+
else v
45+
)
46+
for v in (value, value_min, value_max)
47+
]
48+
# ATTENTION: '60000' is interpreted as '60000M' since default magnitude is 'M'
49+
# ATTENTION: int('60000') is interpreted as '60000B' since _memory_spec_string_to_value return the size in
50+
# ATTENTION: bytes, as target_magnitude = 'b'
51+
# We want to compare the the actual (k,m,g)byte value if there is any
52+
if value_min_ is not None and value_ < value_min_:
53+
return value_min
54+
if value_max_ is not None and value_ > value_max_:
55+
return value_max
56+
return value
57+
else:
58+
if value_min is not None:
59+
return value_min
60+
if value_max is not None:
61+
return value_max
62+
return value
63+
64+
65+
def _is_memory_string(value: str) -> bool:
66+
"""
67+
Check if a string specifies a certain amount of memory.
68+
69+
Args:
70+
value (str): The string to check.
71+
72+
Returns:
73+
bool: True if the string matches a memory specification, False otherwise.
74+
"""
75+
memory_spec_pattern = r"[0-9]+[bBkKmMgGtT]?"
76+
return re.findall(memory_spec_pattern, value)[0] == value
77+
78+
79+
def _memory_spec_string_to_value(
80+
value: str, default_magnitude: str = "m", target_magnitude: str = "b"
81+
) -> Union[int, float]:
82+
"""
83+
Converts a valid memory string (tested by _is_memory_string) into an integer/float value of desired
84+
magnitude `default_magnitude`. If it is a plain integer string (e.g.: '50000') it will be interpreted with
85+
the magnitude passed in by the `default_magnitude`. The output will rescaled to `target_magnitude`
86+
87+
Args:
88+
value (str): The string to convert.
89+
default_magnitude (str): The magnitude for interpreting plain integer strings [b, B, k, K, m, M, g, G, t, T]. Defaults to "m".
90+
target_magnitude (str): The magnitude to which the output value should be converted [b, B, k, K, m, M, g, G, t, T]. Defaults to "b".
91+
92+
Returns:
93+
Union[int, float]: The value of the string in `target_magnitude` units.
94+
"""
95+
magnitude_mapping = {"b": 0, "k": 1, "m": 2, "g": 3, "t": 4}
96+
if _is_memory_string(value):
97+
integer_pattern = r"[0-9]+"
98+
magnitude_pattern = r"[bBkKmMgGtT]+"
99+
integer_value = int(re.findall(integer_pattern, value)[0])
100+
101+
magnitude = re.findall(magnitude_pattern, value)
102+
if len(magnitude) > 0:
103+
magnitude = magnitude[0].lower()
104+
else:
105+
magnitude = default_magnitude.lower()
106+
# Convert it to default magnitude = megabytes
107+
return (integer_value * 1024 ** magnitude_mapping[magnitude]) / (
108+
1024 ** magnitude_mapping[target_magnitude]
109+
)
110+
else:
111+
return value

tests/test_basic.py

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from jinja2.exceptions import TemplateSyntaxError
77
from pysqa import QueueAdapter
88
from pysqa.utils.basic import BasisQueueAdapter
9+
from pysqa.utils.validate import value_in_range
910

1011
__author__ = "Jan Janssen"
1112
__copyright__ = "Copyright 2019, Jan Janssen"
@@ -36,45 +37,35 @@ def test_bad_queue_type(self):
3637
BasisQueueAdapter(config={"queue_type": "error", "queues": {}})
3738

3839
def test_memory_string_comparison(self):
39-
self.assertEqual(BasisQueueAdapter._value_in_range(1023, value_min="1K"), "1K")
40-
self.assertEqual(BasisQueueAdapter._value_in_range(1035, value_min="1K"), 1035)
41-
self.assertEqual(BasisQueueAdapter._value_in_range(1035, value_max="1K"), "1K")
40+
self.assertEqual(value_in_range(1023, value_min="1K"), "1K")
41+
self.assertEqual(value_in_range(1035, value_min="1K"), 1035)
42+
self.assertEqual(value_in_range(1035, value_max="1K"), "1K")
43+
self.assertEqual(value_in_range("1035", value_min="1K"), "1035")
4244
self.assertEqual(
43-
BasisQueueAdapter._value_in_range("1035", value_min="1K"), "1035"
44-
)
45-
self.assertEqual(
46-
BasisQueueAdapter._value_in_range(
47-
"60000M", value_min="1K", value_max="50G"
48-
),
45+
value_in_range("60000M", value_min="1K", value_max="50G"),
4946
"50G",
5047
)
5148
self.assertEqual(
52-
BasisQueueAdapter._value_in_range("60000", value_min="1K", value_max="50G"),
49+
value_in_range("60000", value_min="1K", value_max="50G"),
5350
"50G",
5451
)
5552
self.assertEqual(
56-
BasisQueueAdapter._value_in_range(
57-
"60000M", value_min="1K", value_max="70G"
58-
),
53+
value_in_range("60000M", value_min="1K", value_max="70G"),
5954
"60000M",
6055
)
6156
self.assertEqual(
62-
BasisQueueAdapter._value_in_range(60000, value_min="1K", value_max="70G"),
57+
value_in_range(60000, value_min="1K", value_max="70G"),
6358
60000,
6459
)
6560
self.assertEqual(
66-
BasisQueueAdapter._value_in_range(
67-
90000 * 1024**2, value_min="1K", value_max="70G"
68-
),
61+
value_in_range(90000 * 1024**2, value_min="1K", value_max="70G"),
6962
"70G",
7063
)
7164
self.assertEqual(
72-
BasisQueueAdapter._value_in_range("90000", value_min="1K", value_max="70G"),
65+
value_in_range("90000", value_min="1K", value_max="70G"),
7366
"70G",
7467
)
7568
self.assertEqual(
76-
BasisQueueAdapter._value_in_range(
77-
"60000M", value_min="60G", value_max="70G"
78-
),
69+
value_in_range("60000M", value_min="60G", value_max="70G"),
7970
"60G",
8071
)

tests/test_sge.py

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import unittest
77
import getpass
88
from pysqa import QueueAdapter
9+
from pysqa.utils.validate import value_in_range
910

1011
try:
1112
import defusedxml.ElementTree as ETree
@@ -46,31 +47,23 @@ def test_ssh_delete_file_on_remote(self):
4647
def test_value_in_range(self):
4748
self.assertEqual(
4849
None,
49-
self.sge._adapter._value_in_range(
50-
value=None, value_min=None, value_max=None
51-
),
50+
value_in_range(value=None, value_min=None, value_max=None),
5251
)
5352
self.assertEqual(
5453
1,
55-
self.sge._adapter._value_in_range(value=None, value_min=1, value_max=None),
54+
value_in_range(value=None, value_min=1, value_max=None),
5655
)
5756
self.assertEqual(
5857
1,
59-
self.sge._adapter._value_in_range(value=None, value_min=None, value_max=1),
58+
value_in_range(value=None, value_min=None, value_max=1),
6059
)
6160
self.assertEqual(
6261
1,
63-
self.sge._adapter._value_in_range(value=1, value_min=None, value_max=None),
64-
)
65-
self.assertEqual(
66-
1, self.sge._adapter._value_in_range(value=0, value_min=1, value_max=None)
67-
)
68-
self.assertEqual(
69-
1, self.sge._adapter._value_in_range(value=2, value_min=None, value_max=1)
70-
)
71-
self.assertEqual(
72-
1, self.sge._adapter._value_in_range(value=1, value_min=0, value_max=2)
62+
value_in_range(value=1, value_min=None, value_max=None),
7363
)
64+
self.assertEqual(1, value_in_range(value=0, value_min=1, value_max=None))
65+
self.assertEqual(1, value_in_range(value=2, value_min=None, value_max=1))
66+
self.assertEqual(1, value_in_range(value=1, value_min=0, value_max=2))
7467

7568
def test_job_submission_template(self):
7669
self.assertRaises(

0 commit comments

Comments
 (0)