Made TraitHandler classes replaceable and generic
This commit is contained in:
parent
fb8403e729
commit
7c31e8bd5a
1 changed files with 260 additions and 207 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
"""
|
"""
|
||||||
Traits
|
Traits
|
||||||
|
|
||||||
Whitenoise 2014, Ainneve contributors,
|
Whitenoise 2014, Ainneve contributors,
|
||||||
Griatch 2020
|
Griatch 2020
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -40,21 +40,21 @@ Here's an example for adding the TraitHandler to the base Object class:
|
||||||
```
|
```
|
||||||
|
|
||||||
### Trait Configuration
|
### Trait Configuration
|
||||||
|
|
||||||
A single Trait can be one of three basic types:
|
A single Trait can be one of three basic types:
|
||||||
|
|
||||||
- `Static` - this means a base value and an optional modifier. A typical example would be
|
- `Static` - this means a base value and an optional modifier. A typical example would be
|
||||||
something like a Strength stat or Skill value. That is, something that varies slowly or
|
something like a Strength stat or Skill value. That is, something that varies slowly or
|
||||||
not at all.
|
not at all.
|
||||||
- `Counter` - a Trait of this type has a base value and a current value that
|
- `Counter` - a Trait of this type has a base value and a current value that
|
||||||
can vary inside a specified range. This could be used for skills that can only incrase
|
can vary inside a specified range. This could be used for skills that can only incrase
|
||||||
to a max value.
|
to a max value.
|
||||||
- `Gauge` - Modified counter type modeling a refillable "gauge" that varies between "empty"
|
- `Gauge` - Modified counter type modeling a refillable "gauge" that varies between "empty"
|
||||||
and "full". The classic example is a Health stat.
|
and "full". The classic example is a Health stat.
|
||||||
|
|
||||||
|
|
||||||
```python
|
```python
|
||||||
obj.traits.add("hp", name="Health", type="static",
|
obj.traits.add("hp", name="Health", type="static",
|
||||||
base=0, mod=0, min=None, max=None, extra={})
|
base=0, mod=0, min=None, max=None, extra={})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -177,7 +177,7 @@ Examples:
|
||||||
A "gauge" type `Trait` is a modified counter trait used to model a
|
A "gauge" type `Trait` is a modified counter trait used to model a
|
||||||
gauge that can be emptied and refilled. The `base` property of a
|
gauge that can be emptied and refilled. The `base` property of a
|
||||||
gauge trait represents its "full" value. The `mod` property increases
|
gauge trait represents its "full" value. The `mod` property increases
|
||||||
or decreases that "full" value, rather than the `current`.
|
or decreases that "full" value, rather than the `current`.
|
||||||
|
|
||||||
Gauge type traits are best used to represent traits such as health
|
Gauge type traits are best used to represent traits such as health
|
||||||
points, stamina points, or magic points.
|
points, stamina points, or magic points.
|
||||||
|
|
@ -392,43 +392,28 @@ class TraitHandler:
|
||||||
trait_cls = _TRAIT_CLASSES[trait_type]
|
trait_cls = _TRAIT_CLASSES[trait_type]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise TraitException("Trait class for {trait_type} could not be found.")
|
raise TraitException("Trait class for {trait_type} could not be found.")
|
||||||
trait = self._cache[key] = trait_cls(self.trait_data[key])
|
trait = self._cache[key] = trait_cls(_GA(self, "trait_data")[key])
|
||||||
return trait
|
return trait
|
||||||
|
|
||||||
def add(
|
def add(self, key, name=None, trait_type=DEFAULT_TRAIT_TYPE, force=True, **trait_properties):
|
||||||
self,
|
|
||||||
key,
|
|
||||||
name=None,
|
|
||||||
trait_type=DEFAULT_TRAIT_TYPE,
|
|
||||||
base=0,
|
|
||||||
modifier=0,
|
|
||||||
min_value=None,
|
|
||||||
max_value=None,
|
|
||||||
force=False,
|
|
||||||
**extra_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
|
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): This is a longer name used in Trait
|
name (str, optional): Name of the Trait, like "Health". If
|
||||||
string representation (example 'Health'). If not given, this
|
not given, will use `key` starting with a capital letter.
|
||||||
will be set the same as `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'.
|
||||||
base (int or float, optional): The base value, or 'full' value in the case
|
force_add (bool): If set, create a new Trait even if a Trait with
|
||||||
of a gauge.
|
the same `key` already exists.
|
||||||
modifier (int, optional): A modifier affecting the current or base value.
|
trait_properties (dict): These will all be use to initialize
|
||||||
min_value (int or float, optional): The minimum allowed value.
|
the new trait. See the `properties` class variable on each
|
||||||
max_value (int or float, optional): The maximum allowed value.
|
Trait class to see which are required.
|
||||||
force (bool, optional): Always add, replacing any existing trait.
|
|
||||||
**extra_properties (any): All other kwargs will be made available as key:value
|
|
||||||
properties on the handler. These must all be possible to store
|
|
||||||
in an Attribute.
|
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
TraitException: If specifying invalid values or an existing trait
|
TraitException: If specifying invalid values for the given Trait,
|
||||||
|
the `trait_type` is not recognized, or an existing trait
|
||||||
already exists (and `force` is unset).
|
already exists (and `force` is unset).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
@ -440,25 +425,24 @@ class TraitHandler:
|
||||||
else:
|
else:
|
||||||
raise TraitException(f"Trait '{key}' already exists.")
|
raise TraitException(f"Trait '{key}' already exists.")
|
||||||
|
|
||||||
if trait_type not in _TRAIT_CLASSES:
|
trait_class = _TRAIT_CLASSES.get(trait_type)
|
||||||
|
if not trait_class:
|
||||||
raise TraitException("Trait-type '{trait_type} is invalid.")
|
raise TraitException("Trait-type '{trait_type} is invalid.")
|
||||||
|
|
||||||
trait_kwargs = dict(
|
trait_properties["name"] = key.title() if not name else name
|
||||||
name=name if name is not None else key.title(),
|
trait_properties["trait_type"] = trait_type
|
||||||
trait_type=trait_type,
|
|
||||||
base=base,
|
|
||||||
modifier=modifier,
|
|
||||||
min_value=min_value,
|
|
||||||
max_value=max_value,
|
|
||||||
extra_properties=extra_properties,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.trait_data[key] = trait_kwargs
|
# this will raise exception if input is insufficient
|
||||||
|
trait_properties = trait_class.validate_input(trait_properties)
|
||||||
|
|
||||||
|
print("trait_properties", trait_properties)
|
||||||
|
|
||||||
|
self.trait_data[key] = trait_properties
|
||||||
|
|
||||||
def remove(self, key):
|
def remove(self, 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.
|
key (str): The name of the trait to remove.
|
||||||
|
|
||||||
|
|
@ -481,39 +465,49 @@ class TraitHandler:
|
||||||
# Parent Trait class
|
# Parent Trait class
|
||||||
|
|
||||||
|
|
||||||
@total_ordering
|
|
||||||
class Trait:
|
class Trait:
|
||||||
"""Represents an object or Character trait.
|
"""Represents an object or Character trait.
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
See module docstring for configuration details.
|
See module docstring for configuration details.
|
||||||
"""
|
|
||||||
|
|
||||||
_keys = (
|
"""
|
||||||
"name",
|
# this is the name used to refer to this trait when adding
|
||||||
"trait_type",
|
# a new trait in the TraitHandler
|
||||||
"base",
|
trait_type = "trait"
|
||||||
"modifier",
|
|
||||||
"current",
|
# These keywords form the internal data store of the Trait.
|
||||||
"min_value",
|
# Unless a default value is also given, each must be given
|
||||||
"max_value",
|
# supplied with an explicit value when creating this Trait.
|
||||||
"extra_properties",
|
# This list should at minimum contain "name" and "trait_type".
|
||||||
)
|
data_keys = ("name", "trait_type")
|
||||||
|
# If a dat key has a default, we will use this if it's not supplied at
|
||||||
|
# creation.
|
||||||
|
data_default = {}
|
||||||
|
|
||||||
|
# enable to set/retrieve other arbitrary properties on the Trait
|
||||||
|
# and have them treated like data to store.
|
||||||
|
allow_extra_properties = True
|
||||||
|
|
||||||
def __init__(self, trait_data):
|
def __init__(self, trait_data):
|
||||||
"""
|
"""
|
||||||
Initialize a Trait with stored data.
|
This both initializes and validates the Trait on creation. It must
|
||||||
|
raise exception if validation fails. The TraitHandler will call this
|
||||||
|
when the trait is furst added, to make sure it validates before
|
||||||
|
storing.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
trait_data (_SaverDict or dict): This will be a _SaverDict if
|
trait_data (any): Any pickle-able values to store with this trait.
|
||||||
passed from the TraitHandler, which means this will automatically
|
This must contain any cls.data_keys that do not have a default
|
||||||
save itself the database when updating
|
value in cls.data_default_values. Any extra kwargs will be made
|
||||||
|
available as extra properties on the Trait, assuming the class
|
||||||
|
variable `allow_extra_properties` is set.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
TraitException: If input-validation failed.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
self._data = self.__class__.validate_input(trait_data)
|
||||||
self._type = trait_data["trait_type"]
|
|
||||||
self._data = trait_data
|
|
||||||
self._locked = True
|
|
||||||
|
|
||||||
if not isinstance(trait_data, _SaverDict):
|
if not isinstance(trait_data, _SaverDict):
|
||||||
logger.log_warn(
|
logger.log_warn(
|
||||||
|
|
@ -521,20 +515,34 @@ class Trait:
|
||||||
f"loaded for {type(self).__name__}."
|
f"loaded for {type(self).__name__}."
|
||||||
)
|
)
|
||||||
|
|
||||||
def __repr__(self):
|
@classmethod
|
||||||
"""Debug-friendly representation of this Trait."""
|
def validate_input(cls, trait_data):
|
||||||
return "{}({{{}}})".format(
|
"""
|
||||||
type(self).__name__,
|
Validate input
|
||||||
", ".join(
|
|
||||||
["'{}': {!r}".format(k, self._data[k]) for k in self._keys if k in self._data]
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
def __str__(self):
|
"""
|
||||||
status = "{actual:11}".format(actual=self.actual)
|
inp, req = set(trait_data.keys()), set(cls.data_keys)
|
||||||
return "{name:12} {status} ({mod:+3})".format(name=self.name, status=status, mod=self.mod)
|
unset = req.difference(inp.intersection(req))
|
||||||
|
if unset:
|
||||||
|
# try to add defaults to those we have not set
|
||||||
|
no_defaults = unset.difference(set(cls.data_default))
|
||||||
|
print(f"inp: {inp}, req: {req}, unset: {unset}, no_defaults: {no_defaults}")
|
||||||
|
if no_defaults:
|
||||||
|
raise TraitException(
|
||||||
|
"Trait {} could not be created - misses required keys {}".format(
|
||||||
|
cls.trait_type, ", ".join(no_defaults)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
trait_data.update({key: cls.data_default[key] for key in unset})
|
||||||
|
|
||||||
# Extra Properties - allow access to properties on Trait
|
if not cls.allow_extra_properties:
|
||||||
|
# don't allow any extra properties - remove the extra data
|
||||||
|
for key in inp.difference(req):
|
||||||
|
del trait_data[key]
|
||||||
|
|
||||||
|
return trait_data
|
||||||
|
|
||||||
|
# Grant access to properties on this Trait.
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
"""Access extra parameters as dict keys."""
|
"""Access extra parameters as dict keys."""
|
||||||
|
|
@ -553,11 +561,17 @@ class Trait:
|
||||||
|
|
||||||
def __getattr__(self, key):
|
def __getattr__(self, key):
|
||||||
"""Access extra parameters as attributes."""
|
"""Access extra parameters as attributes."""
|
||||||
|
if key in ("data_keys", "data_default", "trait_type", "allow_extra_properties"):
|
||||||
|
return _GA(self, key)
|
||||||
try:
|
try:
|
||||||
return self._data["extra_properties"][key]
|
return self._data[key]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise AttributeError(
|
raise AttributeError(
|
||||||
"{} '{}' has no attribute {!r}".format(type(self).__name__, self.name, key)
|
"{!r} {} ({}) has no attribute {!r}.".format(
|
||||||
|
self._data['name'],
|
||||||
|
type(self).__name__,
|
||||||
|
self.trait_type,
|
||||||
|
key)
|
||||||
)
|
)
|
||||||
|
|
||||||
def __setattr__(self, key, value):
|
def __setattr__(self, key, value):
|
||||||
|
|
@ -568,36 +582,71 @@ class Trait:
|
||||||
|
|
||||||
This behavior is enabled by setting the instance
|
This behavior is enabled by setting the instance
|
||||||
variable `_locked` to True.
|
variable `_locked` to True.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
propobj = getattr(self.__class__, key, None)
|
propobj = getattr(self.__class__, key, None)
|
||||||
if isinstance(propobj, property):
|
if isinstance(propobj, property):
|
||||||
if propobj.fset is None:
|
# we have a custom property named as this key, find and use its setter
|
||||||
raise AttributeError(f"Can't set attribute {key}.")
|
if propobj.fset:
|
||||||
propobj.fset(self, value)
|
propobj.fset(self, value)
|
||||||
|
return
|
||||||
else:
|
else:
|
||||||
if self.__dict__.get("_locked", False) and key not in ("_keys",):
|
# this is some other value
|
||||||
_GA(self, "_data")["extra_properties"][key] = value
|
if key in ("_data", ):
|
||||||
else:
|
|
||||||
_SA(self, key, value)
|
_SA(self, key, value)
|
||||||
|
return
|
||||||
|
if _GA(self, "allow_extra_properties"):
|
||||||
|
_GA(self, "_data")[key] = value
|
||||||
|
return
|
||||||
|
raise AttributeError(f"Can't set attribute {key} on "
|
||||||
|
f"{self.trait_type} Trait.")
|
||||||
|
|
||||||
def __delattr__(self, key):
|
def __delattr__(self, key):
|
||||||
"""Delete extra parameters as attributes."""
|
"""Delete extra parameters as attributes."""
|
||||||
if key in self._data["extra_properties"]:
|
if key not in _GA(self, properties) and key in self._data:
|
||||||
del self._data["extra_properties"][key]
|
del self._data[key]
|
||||||
|
|
||||||
# Limiting the value to set
|
def __repr__(self):
|
||||||
|
"""Debug-friendly representation of this Trait."""
|
||||||
|
return "{}({{{}}})".format(
|
||||||
|
type(self).__name__,
|
||||||
|
", ".join(
|
||||||
|
["'{}': {!r}".format(k, self._data[k]) for k in self._keys if k in self._data]
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
def _enforce_bounds(self, value):
|
def __str__(self):
|
||||||
"""Ensures that incoming value falls within trait's range."""
|
return f"<Trait {self.name}>"
|
||||||
return value
|
|
||||||
|
|
||||||
def _mod_base(self):
|
# access properties
|
||||||
"""Calculate adding base and modifications"""
|
|
||||||
return self._enforce_bounds(self.mod + self.base)
|
|
||||||
|
|
||||||
def _mod_current(self):
|
@property
|
||||||
"""Calculate the current value"""
|
def name(self):
|
||||||
return self._enforce_bounds(self.mod + self.current)
|
"""Display name for the trait."""
|
||||||
|
return self._data["name"]
|
||||||
|
|
||||||
|
key = name
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@total_ordering
|
||||||
|
class NumericTrait(Trait):
|
||||||
|
"""
|
||||||
|
Base trait for all Traits based on numbers. This implements
|
||||||
|
number-comparisons, limits etc. It also features a "modifier"
|
||||||
|
to the value, since this is a common use.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
trait_type = "numeric"
|
||||||
|
|
||||||
|
data_keys = (
|
||||||
|
"name",
|
||||||
|
"base",
|
||||||
|
)
|
||||||
|
data_default = {
|
||||||
|
"base": 0
|
||||||
|
}
|
||||||
|
|
||||||
# Numeric operations
|
# Numeric operations
|
||||||
|
|
||||||
|
|
@ -689,17 +738,10 @@ class Trait:
|
||||||
|
|
||||||
# Public members
|
# Public members
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
"""Display name for the trait."""
|
|
||||||
return self._data["name"]
|
|
||||||
|
|
||||||
key = name
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def actual(self):
|
def actual(self):
|
||||||
"The actual value of the trait"
|
"The actual value of the trait"
|
||||||
return self._mod_base()
|
return self.base_mod_base()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def base(self):
|
def base(self):
|
||||||
|
|
@ -711,115 +753,94 @@ class Trait:
|
||||||
"""
|
"""
|
||||||
return self._data["base"]
|
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)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def mod(self):
|
|
||||||
"""The trait's modifier."""
|
|
||||||
return self._data["modifier"]
|
|
||||||
|
|
||||||
@mod.setter
|
|
||||||
def mod(self, amount):
|
|
||||||
if type(amount) in (int, float):
|
|
||||||
self._data["modifier"] = amount
|
|
||||||
|
|
||||||
@property
|
|
||||||
def min(self):
|
|
||||||
return self._data["min_value"]
|
|
||||||
|
|
||||||
@min.setter
|
|
||||||
def min(self, value):
|
|
||||||
self._data["min_value"] = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def max(self):
|
|
||||||
return self._data["max_value"]
|
|
||||||
|
|
||||||
@max.setter
|
|
||||||
def max(self, value):
|
|
||||||
self._data["max_value"] = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def current(self):
|
|
||||||
"""The `current` value of the `Trait`."""
|
|
||||||
return self._data.get("current", self.base)
|
|
||||||
|
|
||||||
@current.setter
|
|
||||||
def current(self, value):
|
|
||||||
self._data["current"] = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def extra(self):
|
|
||||||
"""Returns a list containing available extra data keys."""
|
|
||||||
return self._data["extra"].keys()
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
def percent(self):
|
|
||||||
"""Returns the value formatted as a percentage."""
|
|
||||||
return "100.0%"
|
|
||||||
|
|
||||||
|
|
||||||
# Implementation of the respective Trait types
|
# Implementation of the respective Trait types
|
||||||
|
|
||||||
|
|
||||||
class StaticTrait(Trait):
|
class StaticTrait(NumericTrait):
|
||||||
"""
|
"""
|
||||||
Static Trait.
|
Static Trait. This has a modification value.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
trait_type = "static"
|
trait_type = "static"
|
||||||
|
|
||||||
@property
|
data_keys = (
|
||||||
def min(self):
|
"name",
|
||||||
raise TraitException(f"Static Trait {self.key} has no minimum value.")
|
"base",
|
||||||
|
"mod",
|
||||||
|
)
|
||||||
|
data_default = {
|
||||||
|
"base": 0,
|
||||||
|
"mod": 0
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
status = "{actual:11}".format(actual=self.actual)
|
||||||
|
return "{name:12} {status} ({mod:+3})".format(name=self.name, status=status, mod=self.mod)
|
||||||
|
|
||||||
|
# Helpers
|
||||||
|
|
||||||
@min.setter
|
|
||||||
def min(self):
|
|
||||||
raise TraitException(f"Cannot set minimum value for static Trait {self.key}.")
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def max(self):
|
def mod(self):
|
||||||
raise TraitException("Static Trait {self.key} has no maximum value.")
|
"""The trait's modifier."""
|
||||||
|
return self._data["mod"]
|
||||||
|
|
||||||
@max.setter
|
@mod.setter
|
||||||
def max(self):
|
def mod(self, amount):
|
||||||
raise TraitException("Cannot set maximum value for static Trait {self.key}.")
|
if type(amount) in (int, float):
|
||||||
|
self._data["mod"] = amount
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current(self):
|
def actual(self):
|
||||||
"""The `current` value of the `Trait`. This is the same as base for a Static Trait."""
|
"The actual value of the Trait"
|
||||||
return self.base
|
return self.base + self.mod
|
||||||
|
|
||||||
@current.setter
|
|
||||||
def current(self, value):
|
|
||||||
"""Current == base for Static Traits."""
|
|
||||||
self.base = value
|
|
||||||
|
|
||||||
def reset(self):
|
|
||||||
raise TraitException(f"Cannot reset static Trait {self.key}.")
|
|
||||||
|
|
||||||
|
|
||||||
class CounterTrait(Trait):
|
class CounterTrait(NumericTrait):
|
||||||
"""
|
"""
|
||||||
Counter Trait.
|
Counter Trait.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
trait_type = "counter"
|
trait_type = "counter"
|
||||||
|
|
||||||
|
data_keys = (
|
||||||
|
"name",
|
||||||
|
"base",
|
||||||
|
"mod",
|
||||||
|
"current",
|
||||||
|
"min_value",
|
||||||
|
"max_value"
|
||||||
|
)
|
||||||
|
data_default = {
|
||||||
|
"base": 0,
|
||||||
|
"mod": 0,
|
||||||
|
"current": 0,
|
||||||
|
"min_value": None,
|
||||||
|
"max_value": None
|
||||||
|
}
|
||||||
|
|
||||||
|
# Helpers
|
||||||
|
|
||||||
|
def _enforce_bounds(self, value):
|
||||||
|
"""Ensures that incoming value falls within trait's range."""
|
||||||
|
return value
|
||||||
|
|
||||||
|
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_bounds(self, value):
|
||||||
"""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:
|
||||||
|
|
@ -830,15 +851,27 @@ class CounterTrait(Trait):
|
||||||
return self.max
|
return self.max
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
# properties
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def actual(self):
|
def actual(self):
|
||||||
"The actual value of the Trait"
|
"The actual value of the Trait"
|
||||||
return self._mod_current()
|
return self._mod_current()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def base(self):
|
||||||
|
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)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def min(self):
|
def min(self):
|
||||||
"""The lower bound of the range."""
|
return self._data["min_value"]
|
||||||
return super().min
|
|
||||||
|
|
||||||
@min.setter
|
@min.setter
|
||||||
def min(self, amount):
|
def min(self, amount):
|
||||||
|
|
@ -851,7 +884,7 @@ class CounterTrait(Trait):
|
||||||
def max(self):
|
def max(self):
|
||||||
if self._data["max_value"] == "base":
|
if self._data["max_value"] == "base":
|
||||||
return self._mod_base()
|
return self._mod_base()
|
||||||
return super().max
|
return self._data["max_value"]
|
||||||
|
|
||||||
@max.setter
|
@max.setter
|
||||||
def max(self):
|
def max(self):
|
||||||
|
|
@ -862,12 +895,6 @@ class CounterTrait(Trait):
|
||||||
When set this way, the property returns the value of the
|
When set this way, the property returns the value of the
|
||||||
`mod`+`base` properties.
|
`mod`+`base` properties.
|
||||||
"""
|
"""
|
||||||
if self._data["max_value"] == "base":
|
|
||||||
return self._mod_base()
|
|
||||||
return super().max
|
|
||||||
|
|
||||||
@max.setter
|
|
||||||
def max(self, value):
|
|
||||||
if value == "base" or value is None:
|
if value == "base" or value is None:
|
||||||
self._data["max_value"] = value
|
self._data["max_value"] = value
|
||||||
elif type(value) in (int, float):
|
elif type(value) in (int, float):
|
||||||
|
|
@ -876,14 +903,20 @@ class CounterTrait(Trait):
|
||||||
@property
|
@property
|
||||||
def current(self):
|
def current(self):
|
||||||
"""The `current` value of the `Trait`."""
|
"""The `current` value of the `Trait`."""
|
||||||
return super().current
|
return 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)
|
||||||
else:
|
|
||||||
raise AttributeError("'current' property is read-only on static 'Trait'.")
|
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
|
||||||
|
|
||||||
def percent(self):
|
def percent(self):
|
||||||
"""Returns the value formatted as a percentage."""
|
"""Returns the value formatted as a percentage."""
|
||||||
|
|
@ -899,10 +932,29 @@ class GaugeTrait(CounterTrait):
|
||||||
"""
|
"""
|
||||||
Gauge Trait.
|
Gauge Trait.
|
||||||
|
|
||||||
|
This emulates a gauge-meter that can be reset.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
trait_type = "gauge"
|
trait_type = "gauge"
|
||||||
|
|
||||||
|
# same as Counter, here for easy reference
|
||||||
|
data_keys = (
|
||||||
|
"name",
|
||||||
|
"base",
|
||||||
|
"mod",
|
||||||
|
"current",
|
||||||
|
"min_value",
|
||||||
|
"max_value"
|
||||||
|
)
|
||||||
|
data_default = {
|
||||||
|
"base": 0,
|
||||||
|
"mod": 0,
|
||||||
|
"current": 0,
|
||||||
|
"min_value": None,
|
||||||
|
"max_value": None
|
||||||
|
}
|
||||||
|
|
||||||
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)
|
||||||
|
|
@ -915,13 +967,13 @@ class GaugeTrait(CounterTrait):
|
||||||
@property
|
@property
|
||||||
def mod(self):
|
def mod(self):
|
||||||
"""The trait's modifier."""
|
"""The trait's modifier."""
|
||||||
return super().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["modifier"] = amount
|
self._data["mod"] = amount
|
||||||
delta = amount - self._data["modifier"]
|
delta = amount - self._data["mod"]
|
||||||
if delta >= 0:
|
if delta >= 0:
|
||||||
# apply increases to current
|
# apply increases to current
|
||||||
self.current = self._enforce_bounds(self.current + delta)
|
self.current = self._enforce_bounds(self.current + delta)
|
||||||
|
|
@ -936,7 +988,8 @@ class GaugeTrait(CounterTrait):
|
||||||
|
|
||||||
@current.setter
|
@current.setter
|
||||||
def current(self, value):
|
def current(self, value):
|
||||||
super().current = value
|
if type(value) in (int, float):
|
||||||
|
self._data["current"] = self._enforce_bounds(value)
|
||||||
|
|
||||||
def fill_gauge(self):
|
def fill_gauge(self):
|
||||||
"""Adds the `mod`+`base` to the `current` value.
|
"""Adds the `mod`+`base` to the `current` value.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue