Refactor gauge trait to match description of it
This commit is contained in:
parent
c41fd0a33b
commit
7c12e4d362
4 changed files with 641 additions and 249 deletions
|
|
@ -104,6 +104,8 @@ class TraitHandlerTest(_TraitHandlerBase):
|
||||||
self.traithandler.foo = "bar"
|
self.traithandler.foo = "bar"
|
||||||
with self.assertRaises(traits.TraitException):
|
with self.assertRaises(traits.TraitException):
|
||||||
self.traithandler["foo"] = "bar"
|
self.traithandler["foo"] = "bar"
|
||||||
|
with self.assertRaises(traits.TraitException):
|
||||||
|
self.traithandler.test1 = "foo"
|
||||||
|
|
||||||
def test_getting(self):
|
def test_getting(self):
|
||||||
"Test we are getting data from the dbstore"
|
"Test we are getting data from the dbstore"
|
||||||
|
|
@ -294,74 +296,377 @@ class TraitTest(_TraitHandlerBase):
|
||||||
|
|
||||||
|
|
||||||
class TestTraitNumeric(_TraitHandlerBase):
|
class TestTraitNumeric(_TraitHandlerBase):
|
||||||
|
"""
|
||||||
|
Test the numeric base class
|
||||||
|
"""
|
||||||
|
|
||||||
def test_trait__numeric(self):
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
self.traithandler.add(
|
||||||
|
"test1",
|
||||||
|
name="Test1",
|
||||||
|
trait_type='numeric',
|
||||||
|
base=1,
|
||||||
|
extra_val1="xvalue1",
|
||||||
|
extra_val2="xvalue2"
|
||||||
|
)
|
||||||
|
self.trait1 = self.traithandler.get("test1")
|
||||||
|
|
||||||
|
def _get_actuals(self):
|
||||||
|
"""Get trait actuals for comparisons"""
|
||||||
|
return self.trait1.actual, self.trait2.actual
|
||||||
|
|
||||||
|
def test_init(self):
|
||||||
|
self.assertEqual(
|
||||||
|
self.trait1._data,
|
||||||
|
{"name": "Test1",
|
||||||
|
"trait_type": "numeric",
|
||||||
|
"base": 1,
|
||||||
|
"extra_val1": "xvalue1",
|
||||||
|
"extra_val2": "xvalue2"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_set_wrong_type(self):
|
||||||
|
self.trait1.base = "foo"
|
||||||
|
self.assertEqual(self.trait1.base, 1)
|
||||||
|
|
||||||
|
def test_actual(self):
|
||||||
|
self.trait1.base = 10
|
||||||
|
self.assertEqual(self.trait1.actual, 10)
|
||||||
|
|
||||||
|
|
||||||
|
class TestTraitStatic(_TraitHandlerBase):
|
||||||
|
"""
|
||||||
|
Test for static Traits
|
||||||
|
"""
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
self.traithandler.add(
|
||||||
|
"test1",
|
||||||
|
name="Test1",
|
||||||
|
trait_type='static',
|
||||||
|
base=1,
|
||||||
|
mod=2,
|
||||||
|
extra_val1="xvalue1",
|
||||||
|
extra_val2="xvalue2"
|
||||||
|
)
|
||||||
|
self.trait1 = self.traithandler.get("test1")
|
||||||
|
|
||||||
|
def _get_values(self):
|
||||||
|
return self.trait1.base, self.trait1.mod, self.trait1.actual
|
||||||
|
|
||||||
|
def test_init(self):
|
||||||
|
self.assertEqual(
|
||||||
|
self._get_dbstore("test1"),
|
||||||
|
{"name": "Test1",
|
||||||
|
"trait_type": 'static',
|
||||||
|
"base": 1,
|
||||||
|
"mod": 2,
|
||||||
|
"extra_val1": "xvalue1",
|
||||||
|
"extra_val2": "xvalue2"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_actual(self):
|
||||||
|
"""Actual is base + mod"""
|
||||||
|
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))
|
||||||
|
|
||||||
|
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))
|
||||||
|
|
||||||
|
|
||||||
|
class TestTraitCounter(_TraitHandlerBase):
|
||||||
|
"""
|
||||||
|
Test for counter- Traits
|
||||||
|
"""
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
self.traithandler.add(
|
||||||
|
"test1",
|
||||||
|
name="Test1",
|
||||||
|
trait_type='counter',
|
||||||
|
base=1,
|
||||||
|
mod=2,
|
||||||
|
min=-10,
|
||||||
|
max=10,
|
||||||
|
extra_val1="xvalue1",
|
||||||
|
extra_val2="xvalue2"
|
||||||
|
)
|
||||||
|
self.trait1 = self.traithandler.get("test1")
|
||||||
|
|
||||||
|
def _get_values(self):
|
||||||
|
return self.trait1.base, self.trait1.mod, self.trait1.actual
|
||||||
|
|
||||||
|
def test_init(self):
|
||||||
|
self.assertEqual(
|
||||||
|
self._get_dbstore("test1"),
|
||||||
|
{"name": "Test1",
|
||||||
|
"trait_type": 'counter',
|
||||||
|
"base": 1,
|
||||||
|
"mod": 2,
|
||||||
|
"min": -10,
|
||||||
|
"max": 10,
|
||||||
|
"extra_val1": "xvalue1",
|
||||||
|
"extra_val2": "xvalue2"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
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))
|
||||||
|
|
||||||
|
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))
|
||||||
|
|
||||||
|
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))
|
||||||
|
|
||||||
|
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))
|
||||||
|
|
||||||
|
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))
|
||||||
|
|
||||||
|
# re-activate boundaries
|
||||||
|
self.trait1.max = 15
|
||||||
|
self.trait1.min = 10
|
||||||
|
self.assertEqual(self._get_values(), (-10, 100, 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))
|
||||||
|
|
||||||
|
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))
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
class TestTraitGauge(TestTraitCounter):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
self.traithandler.add(
|
self.traithandler.add(
|
||||||
"test2",
|
"test2",
|
||||||
name="Test2",
|
name="Test1",
|
||||||
trait_type='numeric',
|
trait_type='gauge',
|
||||||
)
|
base=1,
|
||||||
self.assertEqual(
|
mod=2,
|
||||||
self._get_dbstore("test2"),
|
min=-10,
|
||||||
{"name": "Test2",
|
max=10,
|
||||||
"trait_type": 'numeric',
|
extra_val1="xvalue1",
|
||||||
"base": 0,
|
extra_val2="xvalue2"
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
self.trait1 = self.traithandler.get("test2")
|
||||||
|
|
||||||
|
def test_boundaries__change_boundaries(self):
|
||||||
|
"""Change boundaries after base/mod change"""
|
||||||
|
self.trait1.base = 5
|
||||||
|
self.trait1.mod = -100
|
||||||
|
self.trait1.min = -20
|
||||||
|
# from pudb import debugger;debugger.Debugger().set_trace()
|
||||||
|
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__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
|
||||||
|
# this won't change since current is not changed
|
||||||
|
self.assertEqual(self._get_values(), (100, 100, 10))
|
||||||
|
self.trait1.current = 150
|
||||||
|
self.assertEqual(self._get_values(), (100, 100, 150))
|
||||||
|
self.trait1.base = -10
|
||||||
|
self.assertEqual(self._get_values(), (-10, 100, 150))
|
||||||
|
|
||||||
|
# re-activate boundaries
|
||||||
|
self.trait1.max = 15
|
||||||
|
self.trait1.min = 10
|
||||||
|
self.assertEqual(self._get_values(), (-10, 100, 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 # this is <base so ok
|
||||||
|
self.assertEqual(self.trait1.max, -20)
|
||||||
|
self.assertEqual(self._get_values(), (-10, 100, -10))
|
||||||
|
|
||||||
|
def test_current(self):
|
||||||
|
"""For a gauge, mod applies to base and not to current."""
|
||||||
|
self.trait1.current = 5
|
||||||
|
self.assertEqual(self._get_values(), (1, 2, 5))
|
||||||
|
self.trait1.current = 14
|
||||||
|
self.assertEqual(self._get_values(), (1, 2, 10))
|
||||||
|
self.trait1.current = -14
|
||||||
|
self.assertEqual(self._get_values(), (1, 2, -10))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class TestNumericTraitOperators(TestCase):
|
||||||
|
"""Test case for numeric magic method implementations."""
|
||||||
|
def setUp(self):
|
||||||
|
# direct instantiation for testing only; use TraitHandler in production
|
||||||
|
self.st = traits.NumericTrait({
|
||||||
|
'name': 'Strength',
|
||||||
|
'trait_type': 'numeric',
|
||||||
|
'base': 8,
|
||||||
|
})
|
||||||
|
self.at = traits.NumericTrait({
|
||||||
|
'name': 'Attack',
|
||||||
|
'trait_type': 'numeric',
|
||||||
|
'base': 4,
|
||||||
|
})
|
||||||
|
|
||||||
def test_trait__static(self):
|
def tearDown(self):
|
||||||
self.traithandler.add(
|
self.st, self.at = None, None
|
||||||
"test3",
|
|
||||||
name="Test3",
|
|
||||||
trait_type='static'
|
|
||||||
)
|
|
||||||
self.assertEqual(
|
|
||||||
self._get_dbstore("test3"),
|
|
||||||
{"name": "Test3",
|
|
||||||
"trait_type": 'static',
|
|
||||||
"base": 0,
|
|
||||||
"mod": 0,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_trait__counter(self):
|
def test_pos_shortcut(self):
|
||||||
self.traithandler.add(
|
"""overridden unary + operator returns `actual` property"""
|
||||||
"test4",
|
self.assertIn(type(+self.st), (float, int))
|
||||||
name="Test4",
|
self.assertEqual(+self.st, self.st.actual)
|
||||||
trait_type='counter'
|
self.assertEqual(+self.st, 8)
|
||||||
)
|
|
||||||
self.assertEqual(
|
def test_add_traits(self):
|
||||||
self._get_dbstore("test4"),
|
"""test addition of `Trait` objects"""
|
||||||
{"name": "Test4",
|
# two Trait objects
|
||||||
"trait_type": 'counter',
|
self.assertEqual(self.st + self.at, 12)
|
||||||
"base": 0,
|
# Trait and numeric
|
||||||
"mod": 0,
|
self.assertEqual(self.st + 1, 9)
|
||||||
"current": 0,
|
self.assertEqual(1 + self.st, 9)
|
||||||
"max_value": None,
|
|
||||||
"min_value": None,
|
def test_sub_traits(self):
|
||||||
}
|
"""test subtraction of `Trait` objects"""
|
||||||
)
|
# two Trait objects
|
||||||
|
self.assertEqual(self.st - self.at, 4)
|
||||||
|
# Trait and numeric
|
||||||
|
self.assertEqual(self.st - 1, 7)
|
||||||
|
self.assertEqual(10 - self.st, 2)
|
||||||
|
|
||||||
|
def test_mul_traits(self):
|
||||||
|
"""test multiplication of `Trait` objects"""
|
||||||
|
# between two Traits
|
||||||
|
self.assertEqual(self.st * self.at, 32)
|
||||||
|
# between Trait and numeric
|
||||||
|
self.assertEqual(self.at * 4, 16)
|
||||||
|
self.assertEqual(4 * self.at, 16)
|
||||||
|
|
||||||
|
def test_floordiv(self):
|
||||||
|
"""test floor division of `Trait` objects"""
|
||||||
|
# between two Traits
|
||||||
|
self.assertEqual(self.st // self.at, 2)
|
||||||
|
# between Trait and numeric
|
||||||
|
self.assertEqual(self.st // 2, 4)
|
||||||
|
self.assertEqual(18 // self.st, 2)
|
||||||
|
|
||||||
|
def test_comparisons_traits(self):
|
||||||
|
"""test equality comparison between `Trait` objects"""
|
||||||
|
self.assertNotEqual(self.st, self.at)
|
||||||
|
self.assertLess(self.at, self.st)
|
||||||
|
self.assertLessEqual(self.at, self.st)
|
||||||
|
self.assertGreater(self.st, self.at)
|
||||||
|
self.assertGreaterEqual(self.st, self.at)
|
||||||
|
|
||||||
|
def test_comparisons_numeric(self):
|
||||||
|
"""equality comparisons between `Trait` and numeric"""
|
||||||
|
self.assertEqual(self.st, 8)
|
||||||
|
self.assertEqual(8, self.st)
|
||||||
|
self.assertNotEqual(self.st, 0)
|
||||||
|
self.assertNotEqual(0, self.st)
|
||||||
|
self.assertLess(self.st, 10)
|
||||||
|
self.assertLess(0, self.st)
|
||||||
|
self.assertLessEqual(self.st, 8)
|
||||||
|
self.assertLessEqual(8, self.st)
|
||||||
|
self.assertLessEqual(self.st, 10)
|
||||||
|
self.assertLessEqual(0, self.st)
|
||||||
|
self.assertGreater(self.st, 0)
|
||||||
|
self.assertGreater(10, self.st)
|
||||||
|
self.assertGreaterEqual(self.st, 8)
|
||||||
|
self.assertGreaterEqual(8, self.st)
|
||||||
|
self.assertGreaterEqual(self.st, 0)
|
||||||
|
self.assertGreaterEqual(10, self.st)
|
||||||
|
|
||||||
def test_trait__gauge(self):
|
|
||||||
self.traithandler.add(
|
|
||||||
"test5",
|
|
||||||
name="Test5",
|
|
||||||
trait_type='gauge'
|
|
||||||
)
|
|
||||||
self.assertEqual(
|
|
||||||
self._get_dbstore("test5"),
|
|
||||||
{"name": "Test5",
|
|
||||||
"trait_type": 'gauge',
|
|
||||||
"base": 0,
|
|
||||||
"mod": 0,
|
|
||||||
"current": 0,
|
|
||||||
"max_value": None,
|
|
||||||
"min_value": None,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
@ -451,93 +756,6 @@ class TestTraitNumeric(_TraitHandlerBase):
|
||||||
# with self.assertRaises(KeyError):
|
# with self.assertRaises(KeyError):
|
||||||
# x = self.trait['preloaded']
|
# x = self.trait['preloaded']
|
||||||
#
|
#
|
||||||
# class TraitOperatorsTestCase(TestCase):
|
|
||||||
# """Test case for numeric magic method implementations."""
|
|
||||||
# def setUp(self):
|
|
||||||
# # direct instantiation for testing only; use TraitHandler in production
|
|
||||||
# self.st = Trait({
|
|
||||||
# 'name': 'Strength',
|
|
||||||
# 'type': 'static',
|
|
||||||
# 'base': 8,
|
|
||||||
# })
|
|
||||||
# self.at = Trait({
|
|
||||||
# 'name': 'Attack',
|
|
||||||
# 'type': 'static',
|
|
||||||
# 'base': 4,
|
|
||||||
# })
|
|
||||||
#
|
|
||||||
# def tearDown(self):
|
|
||||||
# self.st, self.at = None, None
|
|
||||||
#
|
|
||||||
# def test_pos_shortcut(self):
|
|
||||||
# """overridden unary + operator returns `actual` property"""
|
|
||||||
# self.assertIn(type(+self.st), (float, int))
|
|
||||||
# self.assertEqual(+self.st, self.st.actual)
|
|
||||||
# self.assertEqual(+self.st, 8)
|
|
||||||
#
|
|
||||||
# def test_add_traits(self):
|
|
||||||
# """test addition of `Trait` objects"""
|
|
||||||
# # two Trait objects
|
|
||||||
# self.assertEqual(self.st + self.at, 12)
|
|
||||||
# # Trait and numeric
|
|
||||||
# self.assertEqual(self.st + 1, 9)
|
|
||||||
# self.assertEqual(1 + self.st, 9)
|
|
||||||
#
|
|
||||||
# def test_sub_traits(self):
|
|
||||||
# """test subtraction of `Trait` objects"""
|
|
||||||
# # two Trait objects
|
|
||||||
# self.assertEqual(self.st - self.at, 4)
|
|
||||||
# # Trait and numeric
|
|
||||||
# self.assertEqual(self.st - 1, 7)
|
|
||||||
# self.assertEqual(10 - self.st, 2)
|
|
||||||
#
|
|
||||||
# def test_mul_traits(self):
|
|
||||||
# """test multiplication of `Trait` objects"""
|
|
||||||
# # between two Traits
|
|
||||||
# self.assertEqual(self.st * self.at, 32)
|
|
||||||
# # between Trait and numeric
|
|
||||||
# self.assertEqual(self.at * 4, 16)
|
|
||||||
# self.assertEqual(4 * self.at, 16)
|
|
||||||
#
|
|
||||||
# def test_floordiv(self):
|
|
||||||
# """test floor division of `Trait` objects"""
|
|
||||||
# # between two Traits
|
|
||||||
# self.assertEqual(self.st // self.at, 2)
|
|
||||||
# # between Trait and numeric
|
|
||||||
# self.assertEqual(self.st // 2, 4)
|
|
||||||
# self.assertEqual(18 // self.st, 2)
|
|
||||||
#
|
|
||||||
# def test_comparisons_traits(self):
|
|
||||||
# """test equality comparison between `Trait` objects"""
|
|
||||||
# self.assertNotEqual(self.st, self.at)
|
|
||||||
# self.assertLess(self.at, self.st)
|
|
||||||
# self.assertLessEqual(self.at, self.st)
|
|
||||||
# self.assertGreater(self.st, self.at)
|
|
||||||
# self.assertGreaterEqual(self.st, self.at)
|
|
||||||
# # make st.actual = at.actual by modding at
|
|
||||||
# self.at.mod = 4
|
|
||||||
# self.assertEqual(self.st, self.at)
|
|
||||||
# self.assertGreaterEqual(self.st, self.at)
|
|
||||||
# self.assertLessEqual(self.st, self.at)
|
|
||||||
#
|
|
||||||
# def test_comparisons_numeric(self):
|
|
||||||
# """equality comparisons between `Trait` and numeric"""
|
|
||||||
# self.assertEqual(self.st, 8)
|
|
||||||
# self.assertEqual(8, self.st)
|
|
||||||
# self.assertNotEqual(self.st, 0)
|
|
||||||
# self.assertNotEqual(0, self.st)
|
|
||||||
# self.assertLess(self.st, 10)
|
|
||||||
# self.assertLess(0, self.st)
|
|
||||||
# self.assertLessEqual(self.st, 8)
|
|
||||||
# self.assertLessEqual(8, self.st)
|
|
||||||
# self.assertLessEqual(self.st, 10)
|
|
||||||
# self.assertLessEqual(0, self.st)
|
|
||||||
# self.assertGreater(self.st, 0)
|
|
||||||
# self.assertGreater(10, self.st)
|
|
||||||
# self.assertGreaterEqual(self.st, 8)
|
|
||||||
# self.assertGreaterEqual(8, self.st)
|
|
||||||
# self.assertGreaterEqual(self.st, 0)
|
|
||||||
# self.assertGreaterEqual(10, self.st)
|
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
# class CounterTraitTestCase(TestCase):
|
# class CounterTraitTestCase(TestCase):
|
||||||
|
|
|
||||||
|
|
@ -241,9 +241,11 @@ from django.conf import settings
|
||||||
from functools import total_ordering
|
from functools import total_ordering
|
||||||
from evennia.utils.dbserialize import _SaverDict
|
from evennia.utils.dbserialize import _SaverDict
|
||||||
from evennia.utils import logger
|
from evennia.utils import logger
|
||||||
from evennia.utils.utils import inherits_from, class_from_module, list_to_string
|
from evennia.utils.utils import (
|
||||||
|
inherits_from, class_from_module, list_to_string, percent)
|
||||||
|
|
||||||
|
|
||||||
|
# Available Trait classes.
|
||||||
# This way the user can easily supply their own. Each
|
# This way the user can easily supply their own. Each
|
||||||
# class should have a class-property `trait_type` to
|
# class should have a class-property `trait_type` to
|
||||||
# identify the Trait class. The default ones are "static",
|
# identify the Trait class. The default ones are "static",
|
||||||
|
|
@ -346,33 +348,57 @@ class TraitHandler:
|
||||||
"""Return number of Traits registered with the handler"""
|
"""Return number of Traits registered with the handler"""
|
||||||
return len(self.trait_data)
|
return len(self.trait_data)
|
||||||
|
|
||||||
def __setattr__(self, key, value):
|
def __setattr__(self, trait_key, value):
|
||||||
"""Returns error message if trait objects are assigned directly."""
|
"""
|
||||||
if key in ("trait_data", "_cache"):
|
Returns error message if trait objects are assigned directly.
|
||||||
_SA(self, key, value)
|
|
||||||
|
Args:
|
||||||
|
trait_key (str): The Trait-key, like "hp".
|
||||||
|
value (any): Data to store.
|
||||||
|
"""
|
||||||
|
if trait_key in ("trait_data", "_cache"):
|
||||||
|
_SA(self, trait_key, value)
|
||||||
else:
|
else:
|
||||||
|
trait_cls = self._get_trait_class(trait_key=trait_key)
|
||||||
|
valid_keys = list_to_string(list(trait_cls.data_keys.keys()), endsep="or")
|
||||||
raise TraitException(
|
raise TraitException(
|
||||||
"Trait object not settable directly. Assign to one of "
|
"Trait object not settable directly. "
|
||||||
f"`{key}.base`, `{key}.mod`, or `{key}.current` instead."
|
f"Assign to {trait_key}.{valid_keys}."
|
||||||
)
|
)
|
||||||
|
|
||||||
def __setitem__(self, key, value):
|
def __setitem__(self, trait_key, value):
|
||||||
"""Returns error message if trait objects are assigned directly."""
|
"""Returns error message if trait objects are assigned directly."""
|
||||||
return self.__setattr__(key, value)
|
return self.__setattr__(trait_key, value)
|
||||||
|
|
||||||
def __getattr__(self, key):
|
def __getattr__(self, trait_key):
|
||||||
"""Returns Trait instances accessed as attributes."""
|
"""Returns Trait instances accessed as attributes."""
|
||||||
return self.get(key)
|
return self.get(trait_key)
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, trait_key):
|
||||||
"""Returns `Trait` instances accessed as dict keys."""
|
"""Returns `Trait` instances accessed as dict keys."""
|
||||||
return self.get(key)
|
return self.get(trait_key)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "TraitHandler ({num} Trait(s) stored): {keys}".format(
|
return "TraitHandler ({num} Trait(s) stored): {keys}".format(
|
||||||
num=len(self), keys=", ".join(self.all)
|
num=len(self), keys=", ".join(self.all)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _get_trait_class(self, trait_type=None, trait_key=None):
|
||||||
|
"""
|
||||||
|
Helper to retrieve Trait class based on type (like "static")
|
||||||
|
or trait-key (like "hp").
|
||||||
|
|
||||||
|
"""
|
||||||
|
if not trait_type and trait_key:
|
||||||
|
try:
|
||||||
|
trait_type = self.trait_data[trait_key]["trait_type"]
|
||||||
|
except KeyError:
|
||||||
|
raise TraitException(f"Trait class for Trait {trait_key} could not be found.")
|
||||||
|
try:
|
||||||
|
return _TRAIT_CLASSES[trait_type]
|
||||||
|
except KeyError:
|
||||||
|
raise TraitException(f"Trait class for {trait_type} could not be found.")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def all(self):
|
def all(self):
|
||||||
"""
|
"""
|
||||||
|
|
@ -384,38 +410,35 @@ class TraitHandler:
|
||||||
"""
|
"""
|
||||||
return list(self.trait_data.keys())
|
return list(self.trait_data.keys())
|
||||||
|
|
||||||
def get(self, key):
|
def get(self, trait_key):
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
key (str): key from the traits dict containing config data.
|
trait_key (str): key from the traits dict containing config data.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
(`Trait` or `None`): named Trait class or None if trait key
|
(`Trait` or `None`): named Trait class or None if trait key
|
||||||
is not found in traits collection.
|
is not found in traits collection.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
trait = self._cache.get(key)
|
trait = self._cache.get(trait_key)
|
||||||
if trait is None and key in self.trait_data:
|
if trait is None and trait_key in self.trait_data:
|
||||||
trait_type = self.trait_data[key]["trait_type"]
|
trait_type = self.trait_data[trait_key]["trait_type"]
|
||||||
try:
|
trait_cls = self._get_trait_class(trait_type)
|
||||||
trait_cls = _TRAIT_CLASSES[trait_type]
|
trait = self._cache[trait_key] = trait_cls(_GA(self, "trait_data")[trait_key])
|
||||||
except KeyError:
|
|
||||||
raise TraitException("Trait class for {trait_type} could not be found.")
|
|
||||||
trait = self._cache[key] = trait_cls(_GA(self, "trait_data")[key])
|
|
||||||
return trait
|
return trait
|
||||||
|
|
||||||
def add(self, key, name=None, trait_type=DEFAULT_TRAIT_TYPE, force=True, **trait_properties):
|
def add(self, trait_key, name=None, trait_type=DEFAULT_TRAIT_TYPE, force=True, **trait_properties):
|
||||||
"""
|
"""
|
||||||
Create a new Trait and add it to the handler.
|
Create a new Trait and add it to the handler.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
key (str): This is the name of the property that will be made
|
trait_key (str): This is the name of the property that will be made
|
||||||
available on this handler (example 'hp').
|
available on this handler (example 'hp').
|
||||||
name (str, optional): Name of the Trait, like "Health". If
|
name (str, optional): Name of the Trait, like "Health". If
|
||||||
not given, will use `key` starting with a capital letter.
|
not given, will use `trait_key` starting with a capital letter.
|
||||||
trait_type (str, optional): One of 'static', 'counter' or 'gauge'.
|
trait_type (str, optional): One of 'static', 'counter' or 'gauge'.
|
||||||
force_add (bool): If set, create a new Trait even if a Trait with
|
force_add (bool): If set, create a new Trait even if a Trait with
|
||||||
the same `key` already exists.
|
the same `trait_key` already exists.
|
||||||
trait_properties (dict): These will all be use to initialize
|
trait_properties (dict): These will all be use to initialize
|
||||||
the new trait. See the `properties` class variable on each
|
the new trait. See the `properties` class variable on each
|
||||||
Trait class to see which are required.
|
Trait class to see which are required.
|
||||||
|
|
@ -428,46 +451,46 @@ class TraitHandler:
|
||||||
"""
|
"""
|
||||||
# from evennia import set_trace;set_trace()
|
# from evennia import set_trace;set_trace()
|
||||||
|
|
||||||
if key in self.trait_data:
|
if trait_key in self.trait_data:
|
||||||
if force:
|
if force:
|
||||||
self.remove(key)
|
self.remove(trait_key)
|
||||||
else:
|
else:
|
||||||
raise TraitException(f"Trait '{key}' already exists.")
|
raise TraitException(f"Trait '{trait_key}' already exists.")
|
||||||
|
|
||||||
trait_class = _TRAIT_CLASSES.get(trait_type)
|
trait_class = _TRAIT_CLASSES.get(trait_type)
|
||||||
if not trait_class:
|
if not trait_class:
|
||||||
raise TraitException(f"Trait-type '{trait_type}' is invalid.")
|
raise TraitException(f"Trait-type '{trait_type}' is invalid.")
|
||||||
|
|
||||||
trait_properties["name"] = key.title() if not name else name
|
trait_properties["name"] = trait_key.title() if not name else name
|
||||||
trait_properties["trait_type"] = trait_type
|
trait_properties["trait_type"] = trait_type
|
||||||
|
|
||||||
# this will raise exception if input is insufficient
|
# this will raise exception if input is insufficient
|
||||||
trait_properties = trait_class.validate_input(trait_properties)
|
trait_properties = trait_class.validate_input(trait_properties)
|
||||||
|
|
||||||
self.trait_data[key] = trait_properties
|
self.trait_data[trait_key] = trait_properties
|
||||||
|
|
||||||
|
|
||||||
def remove(self, key):
|
def remove(self, trait_key):
|
||||||
"""
|
"""
|
||||||
Remove a Trait from the handler's parent object.
|
Remove a Trait from the handler's parent object.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
key (str): The name of the trait to remove.
|
trait_key (str): The name of the trait to remove.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if key not in self.trait_data:
|
if trait_key not in self.trait_data:
|
||||||
raise TraitException(f"Trait '{key}' not found.")
|
raise TraitException(f"Trait '{trait_key}' not found.")
|
||||||
|
|
||||||
if key in self._cache:
|
if trait_key in self._cache:
|
||||||
del self._cache[key]
|
del self._cache[trait_key]
|
||||||
del self.trait_data[key]
|
del self.trait_data[trait_key]
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
"""
|
"""
|
||||||
Remove all Traits from the handler's parent object.
|
Remove all Traits from the handler's parent object.
|
||||||
"""
|
"""
|
||||||
for key in self.all:
|
for trait_key in self.all:
|
||||||
self.remove(key)
|
self.remove(trait_key)
|
||||||
|
|
||||||
|
|
||||||
# Parent Trait class
|
# Parent Trait class
|
||||||
|
|
@ -482,17 +505,18 @@ class Trait:
|
||||||
Note:
|
Note:
|
||||||
See module docstring for configuration details.
|
See module docstring for configuration details.
|
||||||
|
|
||||||
|
value
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# this is the name used to refer to this trait when adding
|
# this is the name used to refer to this trait when adding
|
||||||
# a new trait in the TraitHandler
|
# a new trait in the TraitHandler
|
||||||
trait_type = "trait"
|
trait_type = "trait"
|
||||||
|
|
||||||
# Keys required when creating a Trait of this type. This is a dict
|
# Property kwargs settable when creating a Trait of this type. This is a
|
||||||
# of key: default. If a key must be given, use traits.TraitKeyRequired
|
# dict of key: default. To indicate a mandatory kwarg and raise an error if
|
||||||
# as its value - this means the key must be explicitly set or
|
# not given, set the default value to the `traits.MandatoryTraitKey` class.
|
||||||
# the trait will not be able to be created.
|
# Apart from the keys given here, "name" and "trait_type" will also always
|
||||||
# Apart from the keys given here, "name" and "trait_type" will also
|
# have to be a apart of the data.
|
||||||
# always have to be a apart of the data.
|
|
||||||
data_keys = {"value": None}
|
data_keys = {"value": None}
|
||||||
|
|
||||||
# enable to set/retrieve other arbitrary properties on the Trait
|
# enable to set/retrieve other arbitrary properties on the Trait
|
||||||
|
|
@ -698,7 +722,11 @@ class NumericTrait(Trait):
|
||||||
"""
|
"""
|
||||||
Base trait for all Traits based on numbers. This implements
|
Base trait for all Traits based on numbers. This implements
|
||||||
number-comparisons, limits etc. It works on the 'base' property since this
|
number-comparisons, limits etc. It works on the 'base' property since this
|
||||||
makes more sense for child classes.
|
makes more sense for child classes. For this base class, the .actual
|
||||||
|
property is just an alias of .base.
|
||||||
|
|
||||||
|
actual = base
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
@ -803,7 +831,7 @@ class NumericTrait(Trait):
|
||||||
@property
|
@property
|
||||||
def actual(self):
|
def actual(self):
|
||||||
"The actual value of the trait"
|
"The actual value of the trait"
|
||||||
return self.base_mod_base()
|
return self.base
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def base(self):
|
def base(self):
|
||||||
|
|
@ -815,14 +843,21 @@ class NumericTrait(Trait):
|
||||||
"""
|
"""
|
||||||
return self._data["base"]
|
return self._data["base"]
|
||||||
|
|
||||||
|
@base.setter
|
||||||
|
def base(self, value):
|
||||||
|
"""Base must be a numerical value."""
|
||||||
|
if type(value) in (int, float):
|
||||||
|
self._data["base"] = value
|
||||||
|
|
||||||
|
|
||||||
# Implementation of the respective Trait types
|
# Implementation of the respective Trait types
|
||||||
|
|
||||||
|
|
||||||
class StaticTrait(NumericTrait):
|
class StaticTrait(NumericTrait):
|
||||||
"""
|
"""
|
||||||
Static Trait. This has a modification value.
|
Static Trait. This has a modification value.
|
||||||
|
|
||||||
|
actual = base + mod
|
||||||
|
|
||||||
"""
|
"""
|
||||||
trait_type = "static"
|
trait_type = "static"
|
||||||
|
|
||||||
|
|
@ -860,16 +895,26 @@ class CounterTrait(NumericTrait):
|
||||||
This includes modifications and min/max limits as well as the notion of a
|
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.
|
current value. The value can also be reset to the base value.
|
||||||
|
|
||||||
|
min/unset base max/unset
|
||||||
|
|-----------------------|----------X-------------------|
|
||||||
|
actual
|
||||||
|
= current
|
||||||
|
+ mod
|
||||||
|
|
||||||
|
- actual = current + mod, starts at base
|
||||||
|
- 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.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
trait_type = "counter"
|
trait_type = "counter"
|
||||||
|
|
||||||
|
# current starts equal to base.
|
||||||
data_keys = {
|
data_keys = {
|
||||||
"base": 0,
|
"base": 0,
|
||||||
"mod": 0,
|
"mod": 0,
|
||||||
"current": 0,
|
"min": None,
|
||||||
"min_value": None,
|
"max": None,
|
||||||
"max_value": None,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Helpers
|
# Helpers
|
||||||
|
|
@ -886,7 +931,7 @@ class CounterTrait(NumericTrait):
|
||||||
"""Ensures that incoming value falls within trait's range."""
|
"""Ensures that incoming value falls within trait's range."""
|
||||||
if self.min is not None and value <= self.min:
|
if self.min is not None and value <= self.min:
|
||||||
return self.min
|
return self.min
|
||||||
if self._data["max_value"] == "base" and value >= self.mod + self.base:
|
if self._data["max"] == "base" and value >= self.mod + self.base:
|
||||||
return self.mod + self.base
|
return self.mod + self.base
|
||||||
if self.max is not None and value >= self.max:
|
if self.max is not None and value >= self.max:
|
||||||
return self.max
|
return self.max
|
||||||
|
|
@ -894,42 +939,39 @@ class CounterTrait(NumericTrait):
|
||||||
|
|
||||||
# properties
|
# properties
|
||||||
|
|
||||||
@property
|
|
||||||
def actual(self):
|
|
||||||
"The actual value of the Trait"
|
|
||||||
return self._mod_current()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def base(self):
|
def base(self):
|
||||||
return self._data["base"]
|
return self._data["base"]
|
||||||
|
|
||||||
@base.setter
|
@base.setter
|
||||||
def base(self, amount):
|
def base(self, amount):
|
||||||
if self._data.get("max_value", None) == "base":
|
if self._data.get("max", None) == "base":
|
||||||
self._data["base"] = amount
|
self._data["base"] = amount
|
||||||
if type(amount) in (int, float):
|
if type(amount) in (int, float):
|
||||||
self._data["base"] = self._enforce_bounds(amount)
|
self._data["base"] = self._enforce_bounds(amount)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def min(self):
|
def min(self):
|
||||||
return self._data["min_value"]
|
return self._data["min"]
|
||||||
|
|
||||||
@min.setter
|
@min.setter
|
||||||
def min(self, amount):
|
def min(self, value):
|
||||||
if amount is None:
|
if value is None:
|
||||||
self._data["min_value"] = amount
|
self._data["min"] = value
|
||||||
elif type(amount) in (int, float):
|
elif type(value) in (int, float):
|
||||||
self._data["min_value"] = amount if amount < self.base else self.base
|
if self.max is not None:
|
||||||
|
value = min(self.max, value)
|
||||||
|
self._data["min"] = value if value < self.base else self.base
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def max(self):
|
def max(self):
|
||||||
if self._data["max_value"] == "base":
|
if self._data["max"] == "base":
|
||||||
return self._mod_base()
|
return self._mod_base()
|
||||||
return self._data["max_value"]
|
return self._data["max"]
|
||||||
|
|
||||||
@max.setter
|
@max.setter
|
||||||
def max(self, value):
|
def max(self, value):
|
||||||
"""The maximum value of the `Trait`.
|
"""The maximum value of the trait.
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
This property may be set to the string literal 'base'.
|
This property may be set to the string literal 'base'.
|
||||||
|
|
@ -937,20 +979,27 @@ class CounterTrait(NumericTrait):
|
||||||
`mod`+`base` properties.
|
`mod`+`base` properties.
|
||||||
"""
|
"""
|
||||||
if value == "base" or value is None:
|
if value == "base" or value is None:
|
||||||
self._data["max_value"] = value
|
self._data["max"] = value
|
||||||
elif type(value) in (int, float):
|
elif type(value) in (int, float):
|
||||||
self._data["max_value"] = value if value > self.base else self.base
|
if self.min is not None:
|
||||||
|
value = max(self.min, value)
|
||||||
|
self._data["max"] = value if value > self.base else self.base
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current(self):
|
def current(self):
|
||||||
"""The `current` value of the `Trait`."""
|
"""The `current` value of the `Trait`."""
|
||||||
return self._data.get("current", self.base)
|
return self._enforce_bounds(self._data.get("current", self.base))
|
||||||
|
|
||||||
@current.setter
|
@current.setter
|
||||||
def current(self, value):
|
def current(self, value):
|
||||||
if type(value) in (int, float):
|
if type(value) in (int, float):
|
||||||
self._data["current"] = self._enforce_bounds(value)
|
self._data["current"] = self._enforce_bounds(value)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def actual(self):
|
||||||
|
"The actual value of the Trait"
|
||||||
|
return self._mod_current()
|
||||||
|
|
||||||
def reset_mod(self):
|
def reset_mod(self):
|
||||||
"""Clears any mod value to 0."""
|
"""Clears any mod value to 0."""
|
||||||
self.mod = 0
|
self.mod = 0
|
||||||
|
|
@ -959,76 +1008,141 @@ class CounterTrait(NumericTrait):
|
||||||
"""Resets `current` property equal to `base` value."""
|
"""Resets `current` property equal to `base` value."""
|
||||||
self.current = self.base
|
self.current = self.base
|
||||||
|
|
||||||
def percent(self):
|
def percent(self, formatting="{:3.1f}%"):
|
||||||
"""Returns the value formatted as a percentage."""
|
"""
|
||||||
if self.max:
|
Return the current value as a percentage.
|
||||||
return "{:3.1f}%".format(self.current * 100.0 / self.max)
|
|
||||||
elif self.base != 0:
|
Args:
|
||||||
return "{:3.1f}%".format(self.current * 100.0 / self._mod_base())
|
formatting (str, optional): Should contain a
|
||||||
# if we get to this point, it's may be a divide by zero situation
|
format-tag which will receive the value. If
|
||||||
return "100.0%"
|
this is set to None, the raw float will be
|
||||||
|
returned.
|
||||||
|
Returns:
|
||||||
|
float or str: Depending of if a `formatting` string
|
||||||
|
is supplied or not.
|
||||||
|
"""
|
||||||
|
return percent(self.current, self.min, self.max, formatting=formatting)
|
||||||
|
|
||||||
|
|
||||||
class GaugeTrait(CounterTrait):
|
class GaugeTrait(CounterTrait):
|
||||||
"""
|
"""
|
||||||
Gauge Trait.
|
Gauge Trait.
|
||||||
|
|
||||||
This emulates a gauge-meter that can be reset.
|
This emulates a gauge-meter that empties from a base+mod value.
|
||||||
|
|
||||||
|
min/0 max=base + mod
|
||||||
|
|-----------------------X---------------------------|
|
||||||
|
actual
|
||||||
|
= current
|
||||||
|
|
||||||
|
- min defaults to 0
|
||||||
|
- max value is always base + mad
|
||||||
|
- .max is an alias of .base
|
||||||
|
- actual = current and varies from min to max.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
trait_type = "gauge"
|
trait_type = "gauge"
|
||||||
|
|
||||||
# same as Counter, here for easy reference
|
# same as Counter, here for easy reference
|
||||||
|
# current starts out equal to base
|
||||||
data_keys = {
|
data_keys = {
|
||||||
"base": 0,
|
"base": 0,
|
||||||
"mod": 0,
|
"mod": 0,
|
||||||
"current": 0,
|
"min": None,
|
||||||
"min_value": None,
|
|
||||||
"max_value": None,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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):
|
||||||
|
"""Ensures that incoming value falls within trait's range."""
|
||||||
|
if self.min is not None and value <= self.min:
|
||||||
|
return self.min
|
||||||
|
return min(self.mod + self.base, value)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
status = "{actual:4} / {base:4}".format(actual=self.actual, base=self.base)
|
status = "{actual:4} / {base:4}".format(actual=self.actual, base=self.base)
|
||||||
return "{name:12} {status} ({mod:+3})".format(name=self.name, status=status, mod=self.mod)
|
return "{name:12} {status} ({mod:+3})".format(name=self.name, status=status, mod=self.mod)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def actual(self):
|
def base(self):
|
||||||
"The actual value of the trait"
|
return self._data["base"]
|
||||||
return self.current
|
|
||||||
|
@base.setter
|
||||||
|
def base(self, value):
|
||||||
|
if type(value) in (int, float):
|
||||||
|
self._data["base"] = self._enforce_bounds(value)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def mod(self):
|
def mod(self):
|
||||||
"""The trait's modifier."""
|
|
||||||
return self._data["mod"]
|
return self._data["mod"]
|
||||||
|
|
||||||
@mod.setter
|
@mod.setter
|
||||||
def mod(self, amount):
|
def mod(self, amount):
|
||||||
if type(amount) in (int, float):
|
if type(amount) in (int, float):
|
||||||
self._data["mod"] = amount
|
self._data["mod"] = amount
|
||||||
delta = amount - self._data["mod"]
|
|
||||||
if delta >= 0:
|
@property
|
||||||
# apply increases to current
|
def min(self):
|
||||||
self.current = self._enforce_bounds(self.current + delta)
|
return self._data["min"]
|
||||||
else:
|
|
||||||
# but not decreases, unless current goes out of range
|
@min.setter
|
||||||
self.current = self._enforce_bounds(self.current)
|
def min(self, value):
|
||||||
|
if value is None:
|
||||||
|
self._data["min"] = self.data_keys['min']
|
||||||
|
elif type(value) in (int, float):
|
||||||
|
self._data["min"] = min(self.value, self.base + self.mod)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def max(self):
|
||||||
|
"The max is always base + mod."
|
||||||
|
return self.base + self.mod
|
||||||
|
|
||||||
|
@max.setter
|
||||||
|
def max(self, value):
|
||||||
|
raise TraitException("The .max property is not settable "
|
||||||
|
"on GaugeTraits. Set .base instead.")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current(self):
|
def current(self):
|
||||||
"""The `current` value of the `Trait`."""
|
"""The `current` value of the gauge."""
|
||||||
return self._data.get("current", self._mod_base())
|
return self._enforce_bounds(self._data.get("current", self._mod_base()))
|
||||||
|
|
||||||
@current.setter
|
@current.setter
|
||||||
def current(self, value):
|
def current(self, value):
|
||||||
if type(value) in (int, float):
|
if type(value) in (int, float):
|
||||||
self._data["current"] = self._enforce_bounds(value)
|
self._data["current"] = self._enforce_bounds(value)
|
||||||
|
|
||||||
def fill_gauge(self):
|
@property
|
||||||
"""Adds the `mod`+`base` to the `current` value.
|
def actual(self):
|
||||||
|
"The actual value of the trait"
|
||||||
|
return self.current
|
||||||
|
|
||||||
Note:
|
def percent(self, formatting="{:3.1f}%"):
|
||||||
Will honor the upper bound if set.
|
"""
|
||||||
|
Return the current value as a percentage.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
formatting (str, optional): Should contain a
|
||||||
|
format-tag which will receive the value. If
|
||||||
|
this is set to None, the raw float will be
|
||||||
|
returned.
|
||||||
|
Returns:
|
||||||
|
float or str: Depending of if a `formatting` string
|
||||||
|
is supplied or not.
|
||||||
|
"""
|
||||||
|
return percent(self.current, self.min, self.max, formatting=formatting)
|
||||||
|
|
||||||
|
|
||||||
|
def fill_gauge(self):
|
||||||
|
"""
|
||||||
|
Fills the gauge to its maximum allowed by base + mod
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.current = self._enforce_bounds(self.current + self._mod_base())
|
self.current = self.base + self.mod
|
||||||
|
|
|
||||||
|
|
@ -312,3 +312,21 @@ class TestFormatGrid(TestCase):
|
||||||
self.assertEqual(len(rows), 8)
|
self.assertEqual(len(rows), 8)
|
||||||
for element in elements:
|
for element in elements:
|
||||||
self.assertTrue(element in "\n".join(rows), f"element {element} is missing.")
|
self.assertTrue(element in "\n".join(rows), f"element {element} is missing.")
|
||||||
|
|
||||||
|
|
||||||
|
class TestPercent(TestCase):
|
||||||
|
"""
|
||||||
|
Test the utils.percentage function.
|
||||||
|
"""
|
||||||
|
def test_ok_input(self):
|
||||||
|
result = utils.percentage(3, 0, 10)
|
||||||
|
self.assertEqual(result, "30.0%")
|
||||||
|
result = utils.percentage(2.5, 5, 10, formatting=None)
|
||||||
|
self.assertEqual(result, 50.0)
|
||||||
|
# min==max we set to 100%
|
||||||
|
self.assertEqual(utils.percentage(4, 5, 5), "100.0%")
|
||||||
|
|
||||||
|
def test_bad_input(self):
|
||||||
|
self.assertRaises(RuntimeError):
|
||||||
|
utils.percentage(3, 10, 1)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1687,6 +1687,48 @@ def format_table(table, extra_space=1):
|
||||||
return ftable
|
return ftable
|
||||||
|
|
||||||
|
|
||||||
|
def percent(self, 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.
|
||||||
|
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
|
||||||
|
raw float will be returned instead.
|
||||||
|
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%.
|
||||||
|
|
||||||
|
"""
|
||||||
|
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 = 100.0
|
||||||
|
if not isinstance(formatting, str):
|
||||||
|
return result
|
||||||
|
return formatting.format(result)
|
||||||
|
|
||||||
|
|
||||||
import functools # noqa
|
import functools # noqa
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue