Add TagProperty, AliasProperty, PermissionProperty. Default autocreate=True for AttributeProperty.
This commit is contained in:
parent
8f1f604708
commit
ef7280f55a
14 changed files with 334 additions and 160 deletions
|
|
@ -176,7 +176,7 @@ class AttributeProperty:
|
|||
|
||||
attrhandler_name = "attributes"
|
||||
|
||||
def __init__(self, default=None, category=None, strattr=False, lockstring="", autocreate=False):
|
||||
def __init__(self, default=None, category=None, strattr=False, lockstring="", autocreate=True):
|
||||
"""
|
||||
Initialize an Attribute as a property descriptor.
|
||||
|
||||
|
|
@ -188,12 +188,12 @@ class AttributeProperty:
|
|||
lockstring (str): This is not itself useful with the property, but only if
|
||||
using the full AttributeHandler.get(accessing_obj=...) to access the
|
||||
Attribute.
|
||||
autocreate (bool): If an un-found Attr should lead to auto-creating the
|
||||
Attribute (with the default value). If `False`, the property will
|
||||
return the default value until it has been explicitly set. This means
|
||||
less database accesses, but also means the property will have no
|
||||
corresponding Attribute if wanting to access it directly via the
|
||||
AttributeHandler (it will also not show up in `examine`).
|
||||
autocreate (bool): True by default; this means Evennia makes sure to create a new
|
||||
copy of the Attribute (with the default value) whenever a new object with this
|
||||
property is created. If `False`, no Attribute will be created until the property
|
||||
is explicitly assigned a value. This makes it more efficient while it retains
|
||||
its default (there's no db access), but without an actual Attribute generated,
|
||||
one cannot access it via .db, the AttributeHandler or see it with `examine`.
|
||||
|
||||
"""
|
||||
self._default = default
|
||||
|
|
@ -218,21 +218,20 @@ class AttributeProperty:
|
|||
"""
|
||||
value = self._default
|
||||
try:
|
||||
value = getattr(instance, self.attrhandler_name).get(
|
||||
value = self.at_get(getattr(instance, self.attrhandler_name).get(
|
||||
key=self._key,
|
||||
default=self._default,
|
||||
category=self._category,
|
||||
strattr=self._strattr,
|
||||
raise_exception=self._autocreate,
|
||||
)
|
||||
))
|
||||
except AttributeError:
|
||||
if self._autocreate:
|
||||
# attribute didn't exist and autocreate is set
|
||||
self.__set__(instance, self._default)
|
||||
else:
|
||||
raise
|
||||
finally:
|
||||
return value
|
||||
return value
|
||||
|
||||
def __set__(self, instance, value):
|
||||
"""
|
||||
|
|
@ -242,7 +241,7 @@ class AttributeProperty:
|
|||
(
|
||||
getattr(instance, self.attrhandler_name).add(
|
||||
self._key,
|
||||
value,
|
||||
self.at_set(value),
|
||||
category=self._category,
|
||||
lockstring=self._lockstring,
|
||||
strattr=self._strattr,
|
||||
|
|
@ -251,10 +250,43 @@ class AttributeProperty:
|
|||
|
||||
def __delete__(self, instance):
|
||||
"""
|
||||
Called when running `del` on the field. Will remove/clear the Attribute.
|
||||
Called when running `del` on the property. Will remove/clear the Attribute. Note that
|
||||
the Attribute will be recreated next retrieval unless the AttributeProperty is also
|
||||
removed in code!
|
||||
|
||||
"""
|
||||
(getattr(instance, self.attrhandler_name).remove(key=self._key, category=self._category))
|
||||
getattr(instance, self.attrhandler_name).remove(key=self._key, category=self._category)
|
||||
|
||||
def at_set(self, value):
|
||||
"""
|
||||
The value to set is passed through the method. It can be used to customize/validate
|
||||
the input in a custom child class.
|
||||
|
||||
Args:
|
||||
value (any): The value about to the stored in this Attribute.
|
||||
|
||||
Returns:
|
||||
any: The value to store.
|
||||
|
||||
Raises:
|
||||
AttributeError: If the value is invalid to store.
|
||||
|
||||
"""
|
||||
return value
|
||||
|
||||
def at_get(self, value):
|
||||
"""
|
||||
The value returned from the Attribute is passed through this method. It can be used
|
||||
to react to the retrieval or modify the result in some way.
|
||||
|
||||
Args:
|
||||
value (any): Value returned from the Attribute.
|
||||
|
||||
Returns:
|
||||
any: The value to return to the caller.
|
||||
|
||||
"""
|
||||
return value
|
||||
|
||||
|
||||
class NAttributeProperty(AttributeProperty):
|
||||
|
|
|
|||
|
|
@ -325,6 +325,18 @@ class TypedObject(SharedMemoryModel):
|
|||
super().__init__(*args, **kwargs)
|
||||
self.set_class_from_typeclass(typeclass_path=typeclass_path)
|
||||
|
||||
def init_evennia_properties(self):
|
||||
"""
|
||||
Called by creation methods; makes sure to initialize Attribute/TagProperties
|
||||
by fetching them once.
|
||||
"""
|
||||
for propkey, prop in self.__class__.__dict__.items():
|
||||
if hasattr(prop, "__set_name__"):
|
||||
try:
|
||||
getattr(self, propkey)
|
||||
except Exception:
|
||||
log_trace()
|
||||
|
||||
# initialize all handlers in a lazy fashion
|
||||
@lazy_property
|
||||
def attributes(self):
|
||||
|
|
|
|||
|
|
@ -96,6 +96,75 @@ class Tag(models.Model):
|
|||
# Handlers making use of the Tags model
|
||||
#
|
||||
|
||||
class TagProperty:
|
||||
"""
|
||||
Tag property descriptor. Allows for setting tags on an object as Django-like 'fields'
|
||||
on the class level. Since Tags are almost always used for querying, Tags are always
|
||||
created/assigned along with the object. Make sure the property/tagname does not collide
|
||||
with an existing method/property on the class. If it does, you must use tags.add()
|
||||
instead.
|
||||
|
||||
Example:
|
||||
::
|
||||
|
||||
class Character(DefaultCharacter):
|
||||
mytag = TagProperty() # category=None
|
||||
mytag2 = TagProperty(category="tagcategory")
|
||||
|
||||
"""
|
||||
taghandler_name = "tags"
|
||||
|
||||
def __init__(self, category=None, data=None):
|
||||
self._category = category
|
||||
self._data = data
|
||||
self._key = ""
|
||||
|
||||
def __set_name__(self, cls, name):
|
||||
"""
|
||||
Called when descriptor is first assigned to the class (not the instance!).
|
||||
It is called with the name of the field.
|
||||
|
||||
"""
|
||||
self._key = name
|
||||
|
||||
def __get__(self, instance, owner):
|
||||
"""
|
||||
Called when accessing the tag as a property on the instance.
|
||||
|
||||
"""
|
||||
try:
|
||||
return getattr(instance, self.taghandler_name).get(
|
||||
key=self._key,
|
||||
category=self._category,
|
||||
return_list=False,
|
||||
raise_exception=True
|
||||
)
|
||||
except AttributeError:
|
||||
self.__set__(instance, self._category)
|
||||
|
||||
def __set__(self, instance, category):
|
||||
"""
|
||||
Assign a new category to the tag. It's not possible to set 'data' this way.
|
||||
|
||||
"""
|
||||
self._category = category
|
||||
(
|
||||
getattr(instance, self.taghandler_name).add(
|
||||
key=self._key,
|
||||
category=self._category,
|
||||
data=self._data
|
||||
)
|
||||
)
|
||||
|
||||
def __delete__(self, instance):
|
||||
"""
|
||||
Called when running `del` on the property. Will disconnect the object from
|
||||
the Tag. Note that the tag will be readded on next fetch unless the
|
||||
TagProperty is also removed in code!
|
||||
|
||||
"""
|
||||
getattr(instance, self.taghandler_name).remove(key=self._key, category=self._category)
|
||||
|
||||
|
||||
class TagHandler(object):
|
||||
"""
|
||||
|
|
@ -361,7 +430,8 @@ class TagHandler(object):
|
|||
|
||||
return ret[0] if len(ret) == 1 else ret
|
||||
|
||||
def get(self, key=None, default=None, category=None, return_tagobj=False, return_list=False):
|
||||
def get(self, key=None, default=None, category=None, return_tagobj=False, return_list=False,
|
||||
raise_exception=False):
|
||||
"""
|
||||
Get the tag for the given key, category or combination of the two.
|
||||
|
||||
|
|
@ -376,6 +446,8 @@ class TagHandler(object):
|
|||
instead of a string representation of the Tag.
|
||||
return_list (bool, optional): Always return a list, regardless
|
||||
of number of matches.
|
||||
raise_exception (bool, optional): Raise AttributeError if no matches
|
||||
are found.
|
||||
|
||||
Returns:
|
||||
tags (list): The matches, either string
|
||||
|
|
@ -383,6 +455,9 @@ class TagHandler(object):
|
|||
depending on `return_tagobj`. If 'default' is set, this
|
||||
will be a list with the default value as its only element.
|
||||
|
||||
Raises:
|
||||
AttributeError: If finding no matches and `raise_exception` is True.
|
||||
|
||||
"""
|
||||
ret = []
|
||||
for keystr in make_iter(key):
|
||||
|
|
@ -393,9 +468,14 @@ class TagHandler(object):
|
|||
for tag in self._getcache(keystr, category)
|
||||
]
|
||||
)
|
||||
if return_list:
|
||||
return ret if ret else [default] if default is not None else []
|
||||
return ret[0] if len(ret) == 1 else (ret if ret else default)
|
||||
if not ret:
|
||||
if raise_exception:
|
||||
raise AttributeError(f"No tags found matching input {key}, {category}.")
|
||||
elif return_list:
|
||||
return [default] if default is not None else []
|
||||
else:
|
||||
return default
|
||||
return ret if return_list else (ret[0] if len(ret) == 1 else ret)
|
||||
|
||||
def remove(self, key=None, category=None):
|
||||
"""
|
||||
|
|
@ -521,6 +601,21 @@ class TagHandler(object):
|
|||
return ",".join(self.all())
|
||||
|
||||
|
||||
class AliasProperty(TagProperty):
|
||||
"""
|
||||
Allows for setting aliases like Django fields:
|
||||
::
|
||||
|
||||
class Character(DefaultCharacter):
|
||||
# note that every character will get the alias bob. Make sure
|
||||
# the alias property does not collide with an existing method
|
||||
# or property on the class.
|
||||
bob = AliasProperty()
|
||||
|
||||
"""
|
||||
taghandler_name = "aliases"
|
||||
|
||||
|
||||
class AliasHandler(TagHandler):
|
||||
"""
|
||||
A handler for the Alias Tag type.
|
||||
|
|
@ -530,6 +625,20 @@ class AliasHandler(TagHandler):
|
|||
_tagtype = "alias"
|
||||
|
||||
|
||||
class PermissionProperty(TagProperty):
|
||||
"""
|
||||
Allows for setting permissions like Django fields:
|
||||
::
|
||||
|
||||
class Character(DefaultCharacter):
|
||||
# note that every character will get this permission! Make
|
||||
# sure it doesn't collide with an existing method or property.
|
||||
myperm = PermissionProperty()
|
||||
|
||||
"""
|
||||
taghandler_name = "permissions"
|
||||
|
||||
|
||||
class PermissionHandler(TagHandler):
|
||||
"""
|
||||
A handler for the Permission Tag type.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ Unit tests for typeclass base system
|
|||
|
||||
"""
|
||||
from django.test import override_settings
|
||||
from evennia.utils.test_resources import BaseEvenniaTest
|
||||
from evennia.utils.test_resources import BaseEvenniaTest, EvenniaTestCase
|
||||
from evennia.typeclasses import attributes
|
||||
from mock import patch
|
||||
from parameterized import parameterized
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue