Skip to content

Commit ace426a

Browse files
authored
Move real/integer bounds attributes to properties (#206)
Move real/integer bounds attributes to properties
1 parent 3d61154 commit ace426a

File tree

5 files changed

+126
-57
lines changed

5 files changed

+126
-57
lines changed

gemd/entity/bounds/integer_bounds.py

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,51 @@
11
"""Bounds an integer to be between two values."""
2+
from math import isfinite
3+
from typing import Union
4+
25
from gemd.entity.bounds.base_bounds import BaseBounds
36

4-
from typing import Union
7+
__all__ = ["IntegerBounds"]
58

69

710
class IntegerBounds(BaseBounds, typ="integer_bounds"):
8-
"""
9-
Bounded subset of the integers, parameterized by a lower and upper bound.
11+
"""Bounded subset of the integers, parameterized by a lower and upper bound."""
1012

11-
Parameters
12-
----------
13-
lower_bound: int
14-
Lower endpoint.
15-
upper_bound: int
16-
Upper endpoint.
13+
def __init__(self, lower_bound: int, upper_bound: int):
14+
self._lower_bound = None
15+
self._upper_bound = None
1716

18-
"""
19-
20-
def __init__(self, lower_bound=None, upper_bound=None):
2117
self.lower_bound = lower_bound
2218
self.upper_bound = upper_bound
2319

24-
if self.lower_bound is None or abs(self.lower_bound) >= float("inf"):
25-
raise ValueError("Lower bound must be given and finite: {}".format(self.lower_bound))
26-
27-
if self.upper_bound is None or abs(self.upper_bound) >= float("inf"):
28-
raise ValueError("Upper bound must be given and finite")
29-
30-
if self.upper_bound < self.lower_bound:
31-
raise ValueError("Upper bound must be greater than or equal to lower bound")
20+
@property
21+
def lower_bound(self) -> int:
22+
"""The lower endpoint of the permitted range."""
23+
return self._lower_bound
24+
25+
@lower_bound.setter
26+
def lower_bound(self, value: int):
27+
"""Set the lower endpoint of the permitted range."""
28+
if value is None or not isfinite(value) or int(value) != float(value):
29+
raise ValueError(f"Lower bound must be given, integer and finite: {value}")
30+
if self.upper_bound is not None and value > self.upper_bound:
31+
raise ValueError(f"Upper bound ({self.upper_bound}) must be "
32+
f"greater than or equal to lower bound ({value})")
33+
self._lower_bound = int(value)
34+
35+
@property
36+
def upper_bound(self) -> int:
37+
"""The upper endpoint of the permitted range."""
38+
return self._upper_bound
39+
40+
@upper_bound.setter
41+
def upper_bound(self, value: int):
42+
"""Set the upper endpoint of the permitted range."""
43+
if value is None or not isfinite(value) or int(value) != float(value):
44+
raise ValueError(f"Upper bound must be given, integer and finite: {value}")
45+
if self.lower_bound is not None and value < self.lower_bound:
46+
raise ValueError(f"Upper bound ({value}) must be "
47+
f"greater than or equal to lower bound ({self.lower_bound})")
48+
self._upper_bound = int(value)
3249

3350
def contains(self, bounds: Union[BaseBounds, "BaseValue"]) -> bool: # noqa: F821
3451
"""

gemd/entity/bounds/real_bounds.py

Lines changed: 47 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,70 @@
11
"""Bound a real number to be between two values."""
2+
from math import isfinite
3+
from typing import Union
4+
25
from gemd.entity.bounds.base_bounds import BaseBounds
36
import gemd.units as units
47

5-
from typing import Union
6-
78

89
class RealBounds(BaseBounds, typ="real_bounds"):
9-
"""
10-
Bounded subset of the real numbers, parameterized by a lower and upper bound.
11-
12-
Parameters
13-
----------
14-
lower_bound: float
15-
Lower endpoint.
16-
upper_bound: float
17-
Upper endpoint.
18-
default_units: str
19-
A string describing the units. Units must be present and parseable by Pint.
20-
An empty string can be used for the units of a dimensionless quantity.
10+
"""Bounded subset of the real numbers, parameterized by a lower and upper bound."""
2111

22-
"""
12+
def __init__(self, lower_bound: float, upper_bound: float, default_units: str):
13+
self._default_units = None
14+
self._lower_bound = None
15+
self._upper_bound = None
2316

24-
def __init__(self, lower_bound=None, upper_bound=None, default_units=None):
17+
self.default_units = default_units
2518
self.lower_bound = lower_bound
2619
self.upper_bound = upper_bound
2720

28-
self._default_units = None
29-
self.default_units = default_units
30-
31-
if self.lower_bound is None or abs(self.lower_bound) >= float("inf"):
32-
raise ValueError("Lower bound must be given and finite: {}".format(self.lower_bound))
33-
34-
if self.upper_bound is None or abs(self.upper_bound) >= float("inf"):
35-
raise ValueError("Upper bound must be given and finite")
21+
@property
22+
def lower_bound(self) -> float:
23+
"""The lower endpoint of the permitted range."""
24+
return self._lower_bound
25+
26+
@lower_bound.setter
27+
def lower_bound(self, value: float):
28+
"""Set the lower endpoint of the permitted range."""
29+
if value is None or not isfinite(value):
30+
raise ValueError(f"Lower bound must be given and finite: {value}")
31+
if self.upper_bound is not None and value > self.upper_bound:
32+
raise ValueError(f"Upper bound ({self.upper_bound}) must be "
33+
f"greater than or equal to lower bound ({value})")
34+
self._lower_bound = float(value)
3635

37-
if self.upper_bound < self.lower_bound:
38-
raise ValueError("Upper bound must be greater than or equal to lower bound")
36+
@property
37+
def upper_bound(self) -> float:
38+
"""The upper endpoint of the permitted range."""
39+
return self._upper_bound
40+
41+
@upper_bound.setter
42+
def upper_bound(self, value: float):
43+
"""Set the upper endpoint of the permitted range."""
44+
if value is None or not isfinite(value):
45+
raise ValueError(f"Upper bound must be given and finite: {value}")
46+
if self.lower_bound is not None and value < self.lower_bound:
47+
raise ValueError(f"Upper bound ({value}) must be "
48+
f"greater than or equal to lower bound ({self.lower_bound})")
49+
self._upper_bound = float(value)
3950

4051
@property
41-
def default_units(self):
42-
"""Get default units."""
52+
def default_units(self) -> str:
53+
"""
54+
A string describing the units.
55+
56+
Units must be present and parseable by Pint.
57+
An empty string can be used for the units of a dimensionless quantity.
58+
"""
4359
return self._default_units
4460

4561
@default_units.setter
46-
def default_units(self, default_units):
62+
def default_units(self, default_units: str):
63+
"""Set the string describing the units."""
4764
if default_units is None:
4865
raise ValueError("Real bounds must have units. "
4966
"Use an empty string for a dimensionless quantity.")
50-
self._default_units = units.parse_units(default_units)
67+
self._default_units = units.parse_units(default_units, return_unit=False)
5168

5269
def contains(self, bounds: Union[BaseBounds, "BaseValue"]) -> bool: # noqa: F821
5370
"""

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
packages.append("")
55

66
setup(name='gemd',
7-
version='1.17.1',
7+
version='1.18.0',
88
python_requires='>=3.8',
99
url='http://github.com/CitrineInformatics/gemd-python',
1010
description="Python binding for Citrine's GEMD data model",

tests/entity/bounds/test_integer_bounds.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,42 @@
88

99
def test_errors():
1010
"""Make sure invalid bounds raise value errors."""
11-
with pytest.raises(ValueError):
11+
with pytest.raises(TypeError):
1212
IntegerBounds()
1313

14+
with pytest.raises(TypeError):
15+
IntegerBounds("0", 10)
16+
17+
with pytest.raises(ValueError):
18+
IntegerBounds(float("-inf"), 0)
19+
20+
with pytest.raises(ValueError):
21+
IntegerBounds(0.5, 1)
22+
1423
with pytest.raises(ValueError):
1524
IntegerBounds(0, float("inf"))
1625

26+
with pytest.raises(ValueError):
27+
IntegerBounds(0, 0.5)
28+
29+
with pytest.raises(TypeError):
30+
IntegerBounds(0, "10")
31+
1732
with pytest.raises(ValueError):
1833
IntegerBounds(10, 1)
1934

35+
with pytest.raises(ValueError):
36+
bnd = IntegerBounds(0, 1)
37+
bnd.lower_bound = 10
38+
39+
with pytest.raises(ValueError):
40+
bnd = IntegerBounds(0, 1)
41+
bnd.upper_bound = -1
42+
43+
bnd = IntegerBounds(0, 1)
44+
assert bnd.lower_bound == 0
45+
assert bnd.upper_bound == 1
46+
2047

2148
def test_incompatible_types():
2249
"""Make sure that incompatible types aren't contained or validated."""

tests/entity/bounds/test_real_bounds.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,20 +57,28 @@ def test_contains_incompatible_units():
5757

5858
def test_constructor_error():
5959
"""Test that invalid real bounds cannot be constructed."""
60-
with pytest.raises(ValueError):
60+
with pytest.raises(TypeError):
6161
RealBounds()
6262

6363
with pytest.raises(ValueError):
64-
RealBounds(0, float("inf"), "meter")
64+
RealBounds(lower_bound=0, upper_bound=float("inf"), default_units="meter")
6565

6666
with pytest.raises(ValueError):
67-
RealBounds(None, 10, '')
67+
RealBounds(lower_bound=None, upper_bound=10, default_units='')
6868

6969
with pytest.raises(ValueError):
70-
RealBounds(0, 100)
70+
RealBounds(lower_bound=0, upper_bound=100, default_units=None)
7171

7272
with pytest.raises(ValueError):
73-
RealBounds(100, 0, "m")
73+
RealBounds(lower_bound=100, upper_bound=0, default_units="m")
74+
75+
with pytest.raises(ValueError):
76+
bnd = RealBounds(lower_bound=0, upper_bound=10, default_units="m")
77+
bnd.lower_bound = 100
78+
79+
bnd = RealBounds(0, 1, "m")
80+
assert bnd.lower_bound == 0.0
81+
assert bnd.upper_bound == 1.0
7482

7583

7684
def test_type_mismatch():

0 commit comments

Comments
 (0)