Make Trait unit tests pass for base trait types

This commit is contained in:
Griatch 2020-04-19 15:47:11 +02:00
parent 9033767a1e
commit d720acef1d
3 changed files with 264 additions and 227 deletions

View file

@ -310,15 +310,15 @@ class TestTraitNumeric(_TraitHandlerBase):
extra_val1="xvalue1",
extra_val2="xvalue2"
)
self.trait1 = self.traithandler.get("test1")
self.trait = self.traithandler.get("test1")
def _get_actuals(self):
"""Get trait actuals for comparisons"""
return self.trait1.actual, self.trait2.actual
return self.trait.actual, self.trait2.actual
def test_init(self):
self.assertEqual(
self.trait1._data,
self.trait._data,
{"name": "Test1",
"trait_type": "numeric",
"base": 1,
@ -328,12 +328,12 @@ class TestTraitNumeric(_TraitHandlerBase):
)
def test_set_wrong_type(self):
self.trait1.base = "foo"
self.assertEqual(self.trait1.base, 1)
self.trait.base = "foo"
self.assertEqual(self.trait.base, 1)
def test_actual(self):
self.trait1.base = 10
self.assertEqual(self.trait1.actual, 10)
self.trait.base = 10
self.assertEqual(self.trait.actual, 10)
class TestTraitStatic(_TraitHandlerBase):
@ -351,10 +351,10 @@ class TestTraitStatic(_TraitHandlerBase):
extra_val1="xvalue1",
extra_val2="xvalue2"
)
self.trait1 = self.traithandler.get("test1")
self.trait = self.traithandler.get("test1")
def _get_values(self):
return self.trait1.base, self.trait1.mod, self.trait1.actual
return self.trait.base, self.trait.mod, self.trait.actual
def test_init(self):
self.assertEqual(
@ -371,16 +371,16 @@ class TestTraitStatic(_TraitHandlerBase):
def test_actual(self):
"""Actual is base + mod"""
self.assertEqual(self._get_values(), (1, 2, 3))
self.trait1.base += 4
self.trait.base += 4
self.assertEqual(self._get_values(), (5, 2, 7))
self.trait1.mod -= 1
self.trait.mod -= 1
self.assertEqual(self._get_values(), (5, 1, 6))
def test_delete(self):
"""Deleting resets to default."""
del self.trait1.base
del self.trait.base
self.assertEqual(self._get_values(), (0, 2, 2))
del self.trait1.mod
del self.trait.mod
self.assertEqual(self._get_values(), (0, 0, 0))
@ -396,15 +396,17 @@ class TestTraitCounter(_TraitHandlerBase):
trait_type='counter',
base=1,
mod=2,
min=-10,
min=0,
max=10,
extra_val1="xvalue1",
extra_val2="xvalue2"
)
self.trait1 = self.traithandler.get("test1")
self.trait = self.traithandler.get("test1")
def _get_values(self):
return self.trait1.base, self.trait1.mod, self.trait1.actual
"""Get (base, mod, actual, min, max)."""
return (self.trait.base, self.trait.mod,
self.trait.actual, self.trait.min, self.trait.max)
def test_init(self):
self.assertEqual(
@ -413,7 +415,7 @@ class TestTraitCounter(_TraitHandlerBase):
"trait_type": 'counter',
"base": 1,
"mod": 2,
"min": -10,
"min": 0,
"max": 10,
"extra_val1": "xvalue1",
"extra_val2": "xvalue2"
@ -422,104 +424,116 @@ class TestTraitCounter(_TraitHandlerBase):
def test_actual(self):
"""Actual is current + mod, where current defaults to base"""
self.assertEqual(self._get_values(), (1, 2, 3))
self.trait1.base += 4
self.assertEqual(self._get_values(), (5, 2, 7))
self.trait1.mod -= 1
self.assertEqual(self._get_values(), (5, 1, 6))
self.assertEqual(self._get_values(), (1, 2, 3, 0, 10))
self.trait.base += 4
self.assertEqual(self._get_values(), (5, 2, 7, 0, 10))
self.trait.mod -= 1
self.assertEqual(self._get_values(), (5, 1, 6, 0, 10))
def test_boundaries__minmax(self):
"""Test range"""
# should not exceed min/max values
self.trait1.base += 20
self.assertEqual(self._get_values(), (10, 2, 10))
self.trait1.base = 100
self.assertEqual(self._get_values(), (10, 2, 10))
self.trait1.base -= 40
self.assertEqual(self._get_values(), (-10, 2, -8))
self.trait1.base = -100
self.assertEqual(self._get_values(), (-10, 2, -8))
self.trait.base += 20
self.assertEqual(self._get_values(), (8, 2, 10, 0, 10))
self.trait.base = 100
self.assertEqual(self._get_values(), (8, 2, 10, 0, 10))
self.trait.base -= 40
self.assertEqual(self._get_values(), (-2, 2, 0, 0, 10))
self.trait.base = -100
self.assertEqual(self._get_values(), (-2, 2, 0, 0, 10))
def test_boundaries__bigmod(self):
"""add a big mod"""
self.trait1.base = 5
self.trait1.mod = 100
self.assertEqual(self._get_values(), (5, 100, 10))
self.trait1.mod = -100
self.assertEqual(self._get_values(), (5, -100, -10))
self.trait.base = 5
self.trait.mod = 100
self.assertEqual(self._get_values(), (5, 5, 10, 0, 10))
self.trait.mod = -100
self.assertEqual(self._get_values(), (5, -5, 0, 0, 10))
def test_boundaries__change_boundaries(self):
"""Change boundaries after base/mod change"""
self.trait1.base = 5
self.trait1.mod = -100
self.trait1.min = -20
self.assertEqual(self._get_values(), (5, -100, -20))
self.trait1.mod = 100
self.trait1.max = 20
self.assertEqual(self._get_values(), (5, 100, 20))
def test_boundaries__base_literal(self):
"""Use the "base" literal makes the max become base+mod"""
self.trait1.base = 5
self.trait1.mod = 100
self.trait1.max = "base"
self.assertEqual(self._get_values(), (5, 100, 105))
self.trait.base = 5
self.trait.mod = -100
self.trait.min = -20
self.assertEqual(self._get_values(), (5, -5, 0, -20, 10))
self.trait.mod -= 100
self.assertEqual(self._get_values(), (5, -25, -20, -20, 10))
self.trait.mod = 100
self.trait.max = 20
self.assertEqual(self._get_values(), (5, 5, 10, -20, 20))
self.trait.mod = 100
self.assertEqual(self._get_values(), (5, 15, 20, -20, 20))
def test_boundaries__disable(self):
"""Disable and re-enable boundaries"""
self.trait1.base = 5
self.trait1.mod = 100
del self.trait1.max
self.assertEqual(self.trait1.max, None)
del self.trait1.min
self.assertEqual(self.trait1.min, None)
self.trait1.base = 100
self.assertEqual(self._get_values(), (100, 100, 200))
self.trait1.base = -10
self.assertEqual(self._get_values(), (-10, 100, 90))
self.trait.base = 5
self.trait.mod = 100
self.assertEqual(self._get_values(), (5, 5, 10, 0, 10))
del self.trait.max
self.assertEqual(self.trait.max, None)
del self.trait.min
self.assertEqual(self.trait.min, None)
self.trait.base = 100
self.assertEqual(self._get_values(), (100, 5, 105, None, None))
self.trait.base = -200
self.assertEqual(self._get_values(), (-200, 5, -195, None, None))
# re-activate boundaries
self.trait1.max = 15
self.trait1.min = 10
self.assertEqual(self._get_values(), (-10, 100, 15))
self.trait.max = 15
self.trait.min = 10 # his is blocked since base+mod is lower
self.assertEqual(self._get_values(), (-200, 5, -195, -195, 15))
def test_boundaries__inverse(self):
"""Set inverse boundaries - limited by base"""
self.trait1.base = -10
self.trait1.mod = 100
self.trait1.min = 20 # will be set to base
self.assertEqual(self.trait1.min, -10)
self.trait1.max = -20
self.assertEqual(self.trait1.max, -10)
self.assertEqual(self._get_values(), (-10, 100, -10))
self.trait.mod = 0
self.assertEqual(self._get_values(), (1, 0, 1, 0, 10))
self.trait.min = 20 # will be set to base
self.assertEqual(self._get_values(), (1, 0, 1, 1, 10))
self.trait.max = -20
self.assertEqual(self._get_values(), (1, 0, 1, 1, 1))
def test_current(self):
"""Modifying current value"""
self.trait1.current = 5
self.assertEqual(self._get_values(), (1, 2, 7))
self.trait1.current = 10
self.assertEqual(self._get_values(), (1, 2, 10))
self.trait1.current = 12
self.assertEqual(self._get_values(), (1, 2, 10))
self.trait.current = 5
self.assertEqual(self._get_values(), (1, 2, 7, 0, 10))
self.trait.current = 10
self.assertEqual(self._get_values(), (1, 2, 10, 0, 10))
self.trait.current = 12
self.assertEqual(self._get_values(), (1, 2, 10, 0, 10))
self.trait.current = -1
self.assertEqual(self._get_values(), (1, 2, 2, 0, 10))
self.trait.current -= 10
self.assertEqual(self._get_values(), (1, 2, 2, 0, 10))
def test_delete(self):
"""Deleting resets to default."""
del self.trait1.base
self.assertEqual(self._get_values(), (0, 2, 2))
del self.trait1.mod
self.assertEqual(self._get_values(), (0, 0, 0))
del self.trait1.min
del self.trait1.max
self.assertEqual(self.trait1.max, None)
self.assertEqual(self.trait1.min, None)
del self.trait.base
self.assertEqual(self._get_values(), (0, 2, 2, 0, 10))
del self.trait.mod
self.assertEqual(self._get_values(), (0, 0, 0, 0, 10))
del self.trait.min
del self.trait.max
self.assertEqual(self._get_values(), (0, 0, 0, None, None))
def test_percentage(self):
"""Test percentage calculation"""
self.assertEqual(self.trait1.percent(), "100.0%")
self.trait1.current = 5
self.assertEqual(self.trait1.percent(), "50.0%")
self.trait1.current = 3
self.assertEqual(self.trait1.percent(), "33.3%")
self.trait.base = 8
self.trait.mod = 2
self.trait.min = 0
self.trait.max = 10
self.assertEqual(self.trait.percent(), "100.0%")
self.trait.current = 3
self.assertEqual(self.trait.percent(), "50.0%")
self.trait.current = 1
self.assertEqual(self.trait.percent(), "30.0%")
# have to lower this since max cannot be lowered below base+mod
self.trait.mod = 1
self.trait.current = 2
self.trait.max -= 1
self.assertEqual(self.trait.percent(), "33.3%")
# open boundary
del self.trait.min
self.assertEqual(self.trait.percent(), "100.0%")
class TestTraitGauge(_TraitHandlerBase):
@ -535,11 +549,12 @@ class TestTraitGauge(_TraitHandlerBase):
extra_val1="xvalue1",
extra_val2="xvalue2"
)
self.trait1 = self.traithandler.get("test1")
self.trait = self.traithandler.get("test1")
def _get_values(self):
return (self.trait1.base, self.trait1.mod, self.trait1.actual,
self.trait1.min, self.trait1.max)
"""Get (base, mod, actual, min, max)."""
return (self.trait.base, self.trait.mod, self.trait.actual,
self.trait.min, self.trait.max)
def test_init(self):
self.assertEqual(
@ -557,119 +572,121 @@ class TestTraitGauge(_TraitHandlerBase):
"""Actual is current, where current defaults to base + mod"""
# current unset - follows base + mod
self.assertEqual(self._get_values(), (8, 2, 10, 0, 10))
self.trait1.base += 4
self.trait.base += 4
self.assertEqual(self._get_values(), (12, 2, 14, 0, 14))
self.trait1.mod -= 1
self.trait.mod -= 1
self.assertEqual(self._get_values(), (12, 1, 13, 0, 13))
# set current, decouple from base + mod
self.trait1.current = 5
self.trait.current = 5
self.assertEqual(self._get_values(), (12, 1, 5, 0, 13))
self.trait1.mod += 1
self.trait1.base -= 4
self.trait.mod += 1
self.trait.base -= 4
self.assertEqual(self._get_values(), (8, 2, 5, 0, 10))
self.trait1.min = -100
self.trait1.base = -20
self.trait.min = -100
self.trait.base = -20
self.assertEqual(self._get_values(), (-20, 2, -18, -100, -18))
def test_boundaries__minmax(self):
"""Test range"""
# current unset - tied to base + mod
self.trait1.base += 20
self.trait.base += 20
self.assertEqual(self._get_values(), (28, 2, 30, 0, 30))
# set current - decouple from base + mod
self.trait1.current = 19
self.trait.current = 19
self.assertEqual(self._get_values(), (28, 2, 19, 0, 30))
# test upper bound
self.trait1.current = 100
self.trait.current = 100
self.assertEqual(self._get_values(), (28, 2, 30, 0, 30))
# min defaults to 0
self.trait1.current = -10
self.trait.current = -10
self.assertEqual(self._get_values(), (28, 2, 0, 0, 30))
self.trait1.min = -20
self.trait.min = -20
self.assertEqual(self._get_values(), (28, 2, 0, -20, 30))
self.trait1.current = -10
self.trait.current = -10
self.assertEqual(self._get_values(), (28, 2, -10, -20, 30))
def test_boundaries__bigmod(self):
"""add a big mod"""
self.trait1.base = 5
self.trait1.mod = 100
self.trait.base = 5
self.trait.mod = 100
self.assertEqual(self._get_values(), (5, 100, 105, 0, 105))
# restricted by min
self.trait1.mod = -100
self.trait.mod = -100
self.assertEqual(self._get_values(), (5, -5, 0, 0, 0))
self.trait1.min = -200
self.trait.min = -200
self.assertEqual(self._get_values(), (5, -5, 0, -200, 0))
def test_boundaries__change_boundaries(self):
"""Change boundaries after current change"""
self.trait1.current = 20
self.trait.current = 20
self.assertEqual(self._get_values(), (8, 2, 10, 0, 10))
self.trait1.mod = 102
self.trait.mod = 102
self.assertEqual(self._get_values(), (8, 102, 10, 0, 110))
# raising min past current value will force it upwards
self.trait1.min = 20
self.trait.min = 20
self.assertEqual(self._get_values(), (8, 102, 20, 20, 110))
def test_boundaries__disable(self):
"""Disable and re-enable boundary"""
self.trait1.base = 5
self.trait1.min = 1
self.trait.base = 5
self.trait.min = 1
self.assertEqual(self._get_values(), (5, 2, 7, 1, 7))
del self.trait1.min
del self.trait.min
self.assertEqual(self._get_values(), (5, 2, 7, 0, 7))
del self.trait1.base
del self.trait1.mod
del self.trait.base
del self.trait.mod
self.assertEqual(self._get_values(), (0, 0, 0, 0, 0))
with self.assertRaises(traits.TraitException):
del self.trait1.max
del self.trait.max
def test_boundaries__inverse(self):
"""Try to set reversed boundaries"""
self.trait1.mod = 0
self.trait1.base = -10 # limited by min
self.trait.mod = 0
self.trait.base = -10 # limited by min
self.assertEqual(self._get_values(), (0, 0, 0, 0, 0))
self.trait1.min = -10
self.trait.min = -10
self.assertEqual(self._get_values(), (0, 0, 0, -10, 0))
self.trait1.base = -10
self.trait.base = -10
self.assertEqual(self._get_values(), (-10, 0, -10, -10, -10))
self.min = 0 # limited by base + mod
self.assertEqual(self._get_values(), (-10, 0, -10, -10, -10))
def test_current(self):
"""Modifying current value"""
self.trait1.base = 10
self.trait1.current = 5
self.trait.base = 10
self.trait.current = 5
self.assertEqual(self._get_values(), (10, 2, 5, 0, 12))
self.trait1.current = 10
self.trait.current = 10
self.assertEqual(self._get_values(), (10, 2, 10, 0, 12))
self.trait1.current = 12
self.trait.current = 12
self.assertEqual(self._get_values(), (10, 2, 12, 0, 12))
self.trait1.current = 0
self.trait.current = 0
self.assertEqual(self._get_values(), (10, 2, 0, 0, 12))
self.trait1.current = -1
self.trait.current = -1
self.assertEqual(self._get_values(), (10, 2, 0, 0, 12))
def test_delete(self):
"""Deleting resets to default."""
del self.trait1.mod
del self.trait.mod
self.assertEqual(self._get_values(), (8, 0, 8, 0, 8))
self.trait1.mod = 2
del self.trait1.base
self.trait.mod = 2
del self.trait.base
self.assertEqual(self._get_values(), (0, 2, 2, 0, 2))
del self.trait1.min
del self.trait.min
self.assertEqual(self._get_values(), (0, 2, 2, 0, 2))
self.trait1.min = -10
self.trait.min = -10
self.assertEqual(self._get_values(), (0, 2, 2, -10, 2))
del self.trait1.min
del self.trait.min
self.assertEqual(self._get_values(), (0, 2, 2, 0, 2))
def test_percentage(self):
"""Test percentage calculation"""
self.assertEqual(self.trait1.percent(), "100.0%")
self.trait1.current = 5
self.assertEqual(self.trait1.percent(), "50.0%")
self.trait1.current = 3
self.assertEqual(self.trait1.percent(), "33.3%")
self.assertEqual(self.trait.percent(), "100.0%")
self.trait.current = 5
self.assertEqual(self.trait.percent(), "50.0%")
self.trait.current = 3
self.assertEqual(self.trait.percent(), "30.0%")
self.trait.mod -= 1
self.assertEqual(self.trait.percent(), "33.3%")
class TestNumericTraitOperators(TestCase):

View file

@ -865,7 +865,8 @@ class NumericTrait(Trait):
class StaticTrait(NumericTrait):
"""
Static Trait. This has a modification value.
Static Trait. This is a single value with a modifier,
with no concept of a 'current' value.
actual = base + mod
@ -906,15 +907,15 @@ class CounterTrait(NumericTrait):
This includes modifications and min/max limits as well as the notion of a
current value. The value can also be reset to the base value.
min/unset base max/unset
|-----------------------|----------X-------------------|
actual
= current
+ mod
min/unset base base+mod max/unset
|--------------|--------|---------X--------X------------|
current actual
= current
+ mod
- actual = current + mod, starts at base
- actual = current + mod, starts at base + mod
- if min or max is None, there is no upper/lower bound (default)
- if max is set to "base", max will be set as base changes.
- if max is set to "base", max will be equal ot base+mod
"""
@ -929,21 +930,12 @@ class CounterTrait(NumericTrait):
}
# Helpers
def _mod_base(self):
"""Calculate adding base and modifications"""
return self._enforce_bounds(self.mod + self.base)
def _mod_current(self):
"""Calculate the current value"""
return self._enforce_bounds(self.mod + self.current)
def _enforce_bounds(self, value):
def _enforce_boundaries(self, value):
"""Ensures that incoming value falls within trait's range."""
if self.min is not None and value <= self.min:
return self.min
if self._data["max"] == "base" and value >= self.mod + self.base:
return self.mod + self.base
return self.base + self.mod
if self.max is not None and value >= self.max:
return self.max
return value
@ -955,11 +947,31 @@ class CounterTrait(NumericTrait):
return self._data["base"]
@base.setter
def base(self, amount):
if self._data.get("max", None) == "base":
self._data["base"] = amount
if type(amount) in (int, float):
self._data["base"] = self._enforce_bounds(amount)
def base(self, value):
if value is None:
self._data["base"] = self.data_keys['base']
if type(value) in (int, float):
if self.min is not None and value + self.mod < self.min:
value = self.min - self.mod
if self.max is not None and value + self.mod > self.max:
value = self.max - self.mod
self._data["base"] = value
@property
def mod(self):
return self._data["mod"]
@mod.setter
def mod(self, value):
if value is None:
# unsetting the boundary to default
self._data["mod"] = self.data_keys['mod']
elif type(value) in (int, float):
if self.min is not None and value + self.base < self.min:
value = self.min - self.base
if self.max is not None and value + self.base > self.max:
value = self.max - self.base
self._data["mod"] = value
@property
def min(self):
@ -968,56 +980,46 @@ class CounterTrait(NumericTrait):
@min.setter
def min(self, value):
if value is None:
# unsetting the boundary
self._data["min"] = value
elif type(value) in (int, float):
if self.max is not None:
value = min(self.max, value)
self._data["min"] = value if value < self.base else self.base
self._data["min"] = min(value, self.base + self.mod)
@property
def max(self):
if self._data["max"] == "base":
return self._mod_base()
return self._data["max"]
@max.setter
def max(self, value):
"""The maximum value of the trait.
Note:
This property may be set to the string literal 'base'.
When set this way, the property returns the value of the
`mod`+`base` properties.
"""
if value == "base" or value is None:
if value is None:
# unsetting the boundary
self._data["max"] = value
elif type(value) in (int, float):
if self.min is not None:
value = max(self.min, value)
self._data["max"] = value if value > self.base else self.base
self._data["max"] = max(value, self.base + self.mod)
@property
def current(self):
"""The `current` value of the `Trait`."""
return self._enforce_bounds(self._data.get("current", self.base))
"""The `current` value of the `Trait`. This does not have .mod added."""
return self._data.get("current", self.base)
@current.setter
def current(self, value):
if type(value) in (int, float):
self._data["current"] = self._enforce_bounds(value)
self._data["current"] = self._enforce_boundaries(value)
@current.deleter
def current(self):
"""reset back to base"""
self._data["current"] = self.base
@property
def actual(self):
"The actual value of the Trait"
return self._mod_current()
def reset_mod(self):
"""Clears any mod value to 0."""
self.mod = 0
def reset(self):
"""Resets `current` property equal to `base` value."""
self.current = self.base
"The actual value of the Trait (current + mod)"
return self._enforce_boundaries(self.current + self.mod)
def percent(self, formatting="{:3.1f}%"):
"""
@ -1032,7 +1034,11 @@ class CounterTrait(NumericTrait):
float or str: Depending of if a `formatting` string
is supplied or not.
"""
return percent(self.current, self.min, self.max, formatting=formatting)
return percent(self.actual, self.min, self.max, formatting=formatting)
def reset(self):
"""Resets `current` property equal to `base` value."""
del self.current
class GaugeTrait(CounterTrait):
@ -1041,7 +1047,7 @@ class GaugeTrait(CounterTrait):
This emulates a gauge-meter that empties from a base+mod value.
min/0 max=base + mod
min/0 max=base+mod
|-----------------------X---------------------------|
actual
= current
@ -1063,15 +1069,7 @@ class GaugeTrait(CounterTrait):
"min": 0,
}
def _mod_base(self):
"""Calculate adding base and modifications"""
return self._enforce_bounds(self.mod + self.base)
def _mod_current(self):
"""Calculate the current value"""
return self._enforce_bounds(self.current)
def _enforce_bounds(self, value):
def _enforce_boundaries(self, value):
"""Ensures that incoming value falls within trait's range."""
if self.min is not None and value <= self.min:
return self.min
@ -1135,12 +1133,18 @@ class GaugeTrait(CounterTrait):
@property
def current(self):
"""The `current` value of the gauge."""
return self._enforce_bounds(self._data.get("current", self._mod_base()))
return self._enforce_boundaries(
self._data.get("current", self.base + self.mod))
@current.setter
def current(self, value):
if type(value) in (int, float):
self._data["current"] = self._enforce_bounds(value)
self._data["current"] = self._enforce_boundaries(value)
@current.deleter
def current(self):
"Resets current back to 'full'"
self._data["current"] = self.base + self.mod
@property
def actual(self):
@ -1162,10 +1166,8 @@ class GaugeTrait(CounterTrait):
"""
return percent(self.current, self.min, self.max, formatting=formatting)
def fill_gauge(self):
def reset(self):
"""
Fills the gauge to its maximum allowed by base + mod
"""
self.current = self.base + self.mod
del self.current

View file

@ -1687,15 +1687,17 @@ def format_table(table, extra_space=1):
return ftable
def percent(self, value, minval, maxval, formatting="{:3.1f}%"):
def percent(value, minval, maxval, formatting="{:3.1f}%"):
"""
Get a value in an interval as a percentage of its position
in that interval. This also understands negative numbers.
Args:
value (number): This should be a value minval<=value<=maxval.
minval (number): Smallest value in interval.
maxval (number): Biggest value in interval.
minval (number or None): Smallest value in interval. This could be None
for an open interval (then return will always be 100%)
maxval (number or None): Biggest value in interval. This could be None
for an open interval (then return will always be 100%)
formatted (str, optional): This is a string that should
accept one formatting tag. This will receive the
current value as a percentage. If None, the
@ -1703,30 +1705,46 @@ def percent(self, value, minval, maxval, formatting="{:3.1f}%"):
Returns:
str or float: The formatted value or the raw percentage
as a float.
Raises:
RuntimeError: If min/max does not make sense.
Notes:
We handle the case of minval==maxval because we may see this case and
don't want to raise exceptions unnecessarily. In that case we return
100%.
We try to handle a weird interval gracefully.
- If either maxval or minval is None (open interval),
we (aribtrarily) assume 100%.
- If minval > maxval, we return 0%.
- If minval == maxval == value we are looking at a single value match
and return 100%.
- If minval == maxval != value we return 0%.
- If value not in [minval..maxval], we set value to the closest
boundary, so the result will be 0% or 100%, respectively.
"""
if minval > maxval:
raise RuntimeError("The minimum value must be <= the max value.")
# constrain value to interval
value = min(max(minval, value), maxval)
# these should both be >0
dpart = value - minval
dfull = maxval - minval
try:
result = (dpart / dfull) * 100.0
except ZeroDivisionError:
# this means minval == maxval
result = None
if None in (minval, maxval):
# we have no boundaries, percent calculation makes no sense,
# we set this to 100% since it
result = 100.0
if not isinstance(formatting, str):
return result
return formatting.format(result)
elif minval > maxval:
# interval has no width so we cannot
# occupy any position within it.
result = 0.0
elif minval == maxval == value:
# this is a single value that we match
result = 100.0
elif minval == maxval != value:
# interval has no width so we cannot be in it.
result = 0.0
if result is None:
# constrain value to interval
value = min(max(minval, value), maxval)
# these should both be >0
dpart = value - minval
dfull = maxval - minval
result = (dpart / dfull) * 100.0
if isinstance(formatting, str):
return formatting.format(result)
return result
import functools # noqa