2020-04-07 23:13:24 +02:00
|
|
|
# Attributes
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
When performing actions in Evennia it is often important that you store data for later. If you write
|
|
|
|
|
a menu system, you have to keep track of the current location in the menu tree so that the player
|
|
|
|
|
can give correct subsequent commands. If you are writing a combat system, you might have a
|
|
|
|
|
combattant's next roll get easier dependent on if their opponent failed. Your characters will
|
|
|
|
|
probably need to store roleplaying-attributes like strength and agility. And so on.
|
|
|
|
|
|
2021-10-21 21:04:14 +02:00
|
|
|
[Typeclassed](./Typeclasses.md) game entities ([Accounts](./Accounts.md), [Objects](./Objects.md),
|
|
|
|
|
[Scripts](./Scripts.md) and [Channels](./Communications.md)) always have *Attributes* associated with them.
|
2020-04-07 23:13:24 +02:00
|
|
|
Attributes are used to store any type of data 'on' such entities. This is different from storing
|
|
|
|
|
data in properties already defined on entities (such as `key` or `location`) - these have very
|
|
|
|
|
specific names and require very specific types of data (for example you couldn't assign a python
|
|
|
|
|
*list* to the `key` property no matter how hard you tried). `Attributes` come into play when you
|
|
|
|
|
want to assign arbitrary data to arbitrary names.
|
|
|
|
|
|
2020-06-16 16:53:35 +02:00
|
|
|
**Attributes are _not_ secure by default and any player may be able to change them unless you
|
2021-10-21 21:04:14 +02:00
|
|
|
[prevent this behavior](./Attributes.md#locking-and-checking-attributes).**
|
2020-05-18 23:55:10 +02:00
|
|
|
|
2020-04-07 23:13:24 +02:00
|
|
|
## The .db and .ndb shortcuts
|
|
|
|
|
|
|
|
|
|
To save persistent data on a Typeclassed object you normally use the `db` (DataBase) operator. Let's
|
2021-10-21 21:04:14 +02:00
|
|
|
try to save some data to a *Rose* (an [Object](./Objects.md)):
|
2020-04-07 23:13:24 +02:00
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
# saving
|
|
|
|
|
rose.db.has_thorns = True
|
|
|
|
|
# getting it back
|
|
|
|
|
is_ouch = rose.db.has_thorns
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
This looks like any normal Python assignment, but that `db` makes sure that an *Attribute* is
|
|
|
|
|
created behind the scenes and is stored in the database. Your rose will continue to have thorns
|
|
|
|
|
throughout the life of the server now, until you deliberately remove them.
|
|
|
|
|
|
|
|
|
|
To be sure to save **non-persistently**, i.e. to make sure NOT to create a database entry, you use
|
|
|
|
|
`ndb` (NonDataBase). It works in the same way:
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
# saving
|
|
|
|
|
rose.ndb.has_thorns = True
|
|
|
|
|
# getting it back
|
|
|
|
|
is_ouch = rose.ndb.has_thorns
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Technically, `ndb` has nothing to do with `Attributes`, despite how similar they look. No
|
|
|
|
|
`Attribute` object is created behind the scenes when using `ndb`. In fact the database is not
|
|
|
|
|
invoked at all since we are not interested in persistence. There is however an important reason to
|
|
|
|
|
use `ndb` to store data rather than to just store variables direct on entities - `ndb`-stored data
|
|
|
|
|
is tracked by the server and will not be purged in various cache-cleanup operations Evennia may do
|
|
|
|
|
while it runs. Data stored on `ndb` (as well as `db`) will also be easily listed by example the
|
|
|
|
|
`@examine` command.
|
|
|
|
|
|
2020-06-16 16:53:35 +02:00
|
|
|
You can also `del` properties on `db` and `ndb` as normal. This will for example delete an
|
|
|
|
|
`Attribute`:
|
2020-04-07 23:13:24 +02:00
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
del rose.db.has_thorns
|
|
|
|
|
```
|
|
|
|
|
|
2020-06-06 19:38:34 +02:00
|
|
|
Both `db` and `ndb` defaults to offering an `all` property on themselves. This returns all
|
2020-04-07 23:13:24 +02:00
|
|
|
associated attributes or non-persistent properties.
|
|
|
|
|
|
|
|
|
|
```python
|
2020-06-06 19:38:34 +02:00
|
|
|
list_of_all_rose_attributes = rose.db.all
|
|
|
|
|
list_of_all_rose_ndb_attrs = rose.ndb.all
|
2020-04-07 23:13:24 +02:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
If you use `all` as the name of an attribute, this will be used instead. Later deleting your custom
|
|
|
|
|
`all` will return the default behaviour.
|
|
|
|
|
|
|
|
|
|
## The AttributeHandler
|
|
|
|
|
|
|
|
|
|
The `.db` and `.ndb` properties are very convenient but if you don't know the name of the Attribute
|
|
|
|
|
beforehand they cannot be used. Behind the scenes `.db` actually accesses the `AttributeHandler`
|
|
|
|
|
which sits on typeclassed entities as the `.attributes` property. `.ndb` does the same for the
|
|
|
|
|
`.nattributes` property.
|
|
|
|
|
|
|
|
|
|
The handlers have normal access methods that allow you to manage and retrieve `Attributes` and
|
|
|
|
|
`NAttributes`:
|
|
|
|
|
|
|
|
|
|
- `has('attrname')` - this checks if the object has an Attribute with this key. This is equivalent
|
|
|
|
|
to doing `obj.db.attrname`.
|
|
|
|
|
- `get(...)` - this retrieves the given Attribute. Normally the `value` property of the Attribute is
|
|
|
|
|
returned, but the method takes keywords for returning the Attribute object itself. By supplying an
|
|
|
|
|
`accessing_object` to the call one can also make sure to check permissions before modifying
|
|
|
|
|
anything.
|
2021-10-21 21:04:14 +02:00
|
|
|
- `add(...)` - this adds a new Attribute to the object. An optional [lockstring](./Locks.md) can be
|
2020-04-07 23:13:24 +02:00
|
|
|
supplied here to restrict future access and also the call itself may be checked against locks.
|
|
|
|
|
- `remove(...)` - Remove the given Attribute. This can optionally be made to check for permission
|
|
|
|
|
before performing the deletion. - `clear(...)` - removes all Attributes from object.
|
|
|
|
|
- `all(...)` - returns all Attributes (of the given category) attached to this object.
|
|
|
|
|
|
2021-10-21 21:04:14 +02:00
|
|
|
See [this section](./Attributes.md#locking-and-checking-attributes) for more about locking down Attribute
|
2020-04-07 23:13:24 +02:00
|
|
|
access and editing. The `Nattribute` offers no concept of access control.
|
|
|
|
|
|
|
|
|
|
Some examples:
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
import evennia
|
|
|
|
|
obj = evennia.search_object("MyObject")
|
|
|
|
|
|
|
|
|
|
obj.attributes.add("test", "testvalue")
|
|
|
|
|
print(obj.db.test) # prints "testvalue"
|
|
|
|
|
print(obj.attributes.get("test")) # "
|
|
|
|
|
print(obj.attributes.all()) # prints [<AttributeObject>]
|
|
|
|
|
obj.attributes.remove("test")
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## Properties of Attributes
|
|
|
|
|
|
|
|
|
|
An Attribute object is stored in the database. It has the following properties:
|
|
|
|
|
|
|
|
|
|
- `key` - the name of the Attribute. When doing e.g. `obj.db.attrname = value`, this property is set
|
|
|
|
|
to `attrname`.
|
|
|
|
|
- `value` - this is the value of the Attribute. This value can be anything which can be pickled -
|
|
|
|
|
objects, lists, numbers or what have you (see
|
2021-10-21 21:04:14 +02:00
|
|
|
[this section](./Attributes.md#what-types-of-data-can-i-save-in-an-attribute) for more info). In the
|
2020-06-16 16:53:35 +02:00
|
|
|
example
|
2020-04-07 23:13:24 +02:00
|
|
|
`obj.db.attrname = value`, the `value` is stored here.
|
|
|
|
|
- `category` - this is an optional property that is set to None for most Attributes. Setting this
|
|
|
|
|
allows to use Attributes for different functionality. This is usually not needed unless you want
|
2021-10-21 21:04:14 +02:00
|
|
|
to use Attributes for very different functionality ([Nicks](./Nicks.md) is an example of using
|
2020-06-16 16:53:35 +02:00
|
|
|
Attributes
|
2021-10-21 21:04:14 +02:00
|
|
|
in this way). To modify this property you need to use the
|
|
|
|
|
[Attribute Handler](./Attributes.md#the-attributehandler).
|
2020-04-07 23:13:24 +02:00
|
|
|
- `strvalue` - this is a separate value field that only accepts strings. This severely limits the
|
|
|
|
|
data possible to store, but allows for easier database lookups. This property is usually not used
|
2021-10-21 21:04:14 +02:00
|
|
|
except when re-using Attributes for some other purpose ([Nicks](./Nicks.md) use it). It is only
|
|
|
|
|
accessible via the [Attribute Handler](./Attributes.md#the-attributehandler).
|
2020-04-07 23:13:24 +02:00
|
|
|
|
|
|
|
|
There are also two special properties:
|
|
|
|
|
|
2021-10-21 21:04:14 +02:00
|
|
|
- `attrtype` - this is used internally by Evennia to separate [Nicks](./Nicks.md), from Attributes (Nicks
|
2020-04-07 23:13:24 +02:00
|
|
|
use Attributes behind the scenes).
|
|
|
|
|
- `model` - this is a *natural-key* describing the model this Attribute is attached to. This is on
|
|
|
|
|
the form *appname.modelclass*, like `objects.objectdb`. It is used by the Attribute and
|
|
|
|
|
NickHandler to quickly sort matches in the database. Neither this nor `attrtype` should normally
|
|
|
|
|
need to be modified.
|
|
|
|
|
|
|
|
|
|
Non-database attributes have no equivalence to `category` nor `strvalue`, `attrtype` or `model`.
|
|
|
|
|
|
|
|
|
|
## Persistent vs non-persistent
|
|
|
|
|
|
|
|
|
|
So *persistent* data means that your data will survive a server reboot, whereas with
|
|
|
|
|
*non-persistent* data it will not ...
|
|
|
|
|
|
|
|
|
|
... So why would you ever want to use non-persistent data? The answer is, you don't have to. Most of
|
|
|
|
|
the time you really want to save as much as you possibly can. Non-persistent data is potentially
|
|
|
|
|
useful in a few situations though.
|
|
|
|
|
|
|
|
|
|
- You are worried about database performance. Since Evennia caches Attributes very aggressively,
|
|
|
|
|
this is not an issue unless you are reading *and* writing to your Attribute very often (like many
|
|
|
|
|
times per second). Reading from an already cached Attribute is as fast as reading any Python
|
|
|
|
|
property. But even then this is not likely something to worry about: Apart from Evennia's own
|
2020-06-16 16:53:35 +02:00
|
|
|
caching, modern database systems themselves also cache data very efficiently for speed. Our
|
|
|
|
|
default
|
2020-04-07 23:13:24 +02:00
|
|
|
database even runs completely in RAM if possible, alleviating much of the need to write to disk
|
|
|
|
|
during heavy loads.
|
|
|
|
|
- A more valid reason for using non-persistent data is if you *want* to lose your state when logging
|
|
|
|
|
off. Maybe you are storing throw-away data that are re-initialized at server startup. Maybe you
|
2021-10-21 21:04:14 +02:00
|
|
|
are implementing some caching of your own. Or maybe you are testing a buggy [Script](./Scripts.md) that
|
2020-06-16 16:53:35 +02:00
|
|
|
does potentially harmful stuff to your character object. With non-persistent storage you can be
|
|
|
|
|
sure
|
2020-04-07 23:13:24 +02:00
|
|
|
that whatever is messed up, it's nothing a server reboot can't clear up.
|
|
|
|
|
- NAttributes have no restrictions at all on what they can store (see next section), since they
|
|
|
|
|
don't need to worry about being saved to the database - they work very well for temporary storage.
|
|
|
|
|
- You want to implement a fully or partly *non-persistent world*. Who are we to argue with your
|
|
|
|
|
grand vision!
|
|
|
|
|
|
|
|
|
|
## What types of data can I save in an Attribute?
|
|
|
|
|
|
|
|
|
|
> None of the following affects NAttributes, which does not invoke the database at all. There are no
|
|
|
|
|
> restrictions to what can be stored in a NAttribute.
|
|
|
|
|
|
|
|
|
|
The database doesn't know anything about Python objects, so Evennia must *serialize* Attribute
|
|
|
|
|
values into a string representation in order to store it to the database. This is done using the
|
|
|
|
|
`pickle` module of Python (the only exception is if you use the `strattr` keyword of the
|
|
|
|
|
AttributeHandler to save to the `strvalue` field of the Attribute. In that case you can only save
|
|
|
|
|
*strings* which will not be pickled).
|
|
|
|
|
|
|
|
|
|
It's important to note that when you access the data in an Attribute you are *always* de-serializing
|
|
|
|
|
it from the database representation every time. This is because we allow for storing
|
|
|
|
|
database-entities in Attributes too. If we cached it as its Python form, we might end up with
|
|
|
|
|
situations where the database entity was deleted since we last accessed the Attribute.
|
|
|
|
|
De-serializing data with a database-entity in it means querying the database for that object and
|
|
|
|
|
making sure it still exists (otherwise it will be set to `None`). Performance-wise this is usually
|
|
|
|
|
not a big deal. But if you are accessing the Attribute as part of some big loop or doing a large
|
|
|
|
|
amount of reads/writes you should first extract it to a temporary variable, operate on *that* and
|
|
|
|
|
then save the result back to the Attribute. If you are storing a more complex structure like a
|
|
|
|
|
`dict` or a `list` you should make sure to "disconnect" it from the database before looping over it,
|
2021-10-21 21:04:14 +02:00
|
|
|
as mentioned in the [Retrieving Mutable Objects](./Attributes.md#retrieving-mutable-objects) section
|
2020-06-16 16:53:35 +02:00
|
|
|
below.
|
2020-04-07 23:13:24 +02:00
|
|
|
|
|
|
|
|
### Storing single objects
|
|
|
|
|
|
2020-06-16 16:53:35 +02:00
|
|
|
With a single object, we mean anything that is *not iterable*, like numbers, strings or custom class
|
|
|
|
|
instances without the `__iter__` method.
|
2020-04-07 23:13:24 +02:00
|
|
|
|
|
|
|
|
* You can generally store any non-iterable Python entity that can be
|
2021-06-23 20:05:25 +12:00
|
|
|
[pickled](https://docs.python.org/library/pickle.html).
|
2020-04-07 23:13:24 +02:00
|
|
|
* Single database objects/typeclasses can be stored as any other in the Attribute. These can
|
|
|
|
|
normally *not* be pickled, but Evennia will behind the scenes convert them to an internal
|
|
|
|
|
representation using their classname, database-id and creation-date with a microsecond precision,
|
|
|
|
|
guaranteeing you get the same object back when you access the Attribute later.
|
|
|
|
|
* If you *hide* a database object inside a non-iterable custom class (like stored as a variable
|
|
|
|
|
inside it), Evennia will not know it's there and won't convert it safely. Storing classes with
|
|
|
|
|
such hidden database objects is *not* supported and will lead to errors!
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
# Examples of valid single-value attribute data:
|
|
|
|
|
obj.db.test1 = 23
|
|
|
|
|
obj.db.test1 = False
|
|
|
|
|
# a database object (will be stored as an internal representation)
|
|
|
|
|
obj.db.test2 = myobj
|
|
|
|
|
|
|
|
|
|
# example of an invalid, "hidden" dbobject
|
|
|
|
|
class Invalid(object):
|
|
|
|
|
def __init__(self, dbobj):
|
|
|
|
|
# no way for Evennia to know this is a dbobj
|
|
|
|
|
self.dbobj = dbobj
|
|
|
|
|
invalid = Invalid(myobj)
|
|
|
|
|
obj.db.invalid = invalid # will cause error!
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Storing multiple objects
|
|
|
|
|
|
|
|
|
|
This means storing objects in a collection of some kind and are examples of *iterables*, pickle-able
|
|
|
|
|
entities you can loop over in a for-loop. Attribute-saving supports the following iterables:
|
|
|
|
|
|
|
|
|
|
* [Tuples](https://docs.python.org/2/library/functions.html#tuple), like `(1,2,"test", <dbobj>)`.
|
2020-06-16 16:53:35 +02:00
|
|
|
* [Lists](https://docs.python.org/2/tutorial/datastructures.html#more-on-lists), like `[1,2,"test",
|
|
|
|
|
<dbobj>]`.
|
|
|
|
|
* [Dicts](https://docs.python.org/2/tutorial/datastructures.html#dictionaries), like `{1:2,
|
|
|
|
|
"test":<dbobj>]`.
|
2020-04-07 23:13:24 +02:00
|
|
|
* [Sets](https://docs.python.org/2/tutorial/datastructures.html#sets), like `{1,2,"test",<dbobj>}`.
|
2020-06-16 16:53:35 +02:00
|
|
|
*
|
|
|
|
|
[collections.OrderedDict](https://docs.python.org/2/library/collections.html#collections.OrderedDict),
|
|
|
|
|
like `OrderedDict((1,2), ("test", <dbobj>))`.
|
|
|
|
|
* [collections.Deque](https://docs.python.org/2/library/collections.html#collections.deque), like
|
|
|
|
|
`deque((1,2,"test",<dbobj>))`.
|
|
|
|
|
* *Nestings* of any combinations of the above, like lists in dicts or an OrderedDict of tuples, each
|
|
|
|
|
containing dicts, etc.
|
2020-04-07 23:13:24 +02:00
|
|
|
* All other iterables (i.e. entities with the `__iter__` method) will be converted to a *list*.
|
|
|
|
|
Since you can use any combination of the above iterables, this is generally not much of a
|
|
|
|
|
limitation.
|
|
|
|
|
|
2021-10-21 21:04:14 +02:00
|
|
|
Any entity listed in the [Single object](./Attributes.md#storing-single-objects) section above can be
|
2020-06-16 16:53:35 +02:00
|
|
|
stored in the iterable.
|
2020-04-07 23:13:24 +02:00
|
|
|
|
|
|
|
|
> As mentioned in the previous section, database entities (aka typeclasses) are not possible to
|
|
|
|
|
> pickle. So when storing an iterable, Evennia must recursively traverse the iterable *and all its
|
|
|
|
|
> nested sub-iterables* in order to find eventual database objects to convert. This is a very fast
|
|
|
|
|
> process but for efficiency you may want to avoid too deeply nested structures if you can.
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
# examples of valid iterables to store
|
|
|
|
|
obj.db.test3 = [obj1, 45, obj2, 67]
|
|
|
|
|
# a dictionary
|
|
|
|
|
obj.db.test4 = {'str':34, 'dex':56, 'agi':22, 'int':77}
|
|
|
|
|
# a mixed dictionary/list
|
|
|
|
|
obj.db.test5 = {'members': [obj1,obj2,obj3], 'enemies':[obj4,obj5]}
|
|
|
|
|
# a tuple with a list in it
|
|
|
|
|
obj.db.test6 = (1,3,4,8, ["test", "test2"], 9)
|
|
|
|
|
# a set
|
|
|
|
|
obj.db.test7 = set([1,2,3,4,5])
|
|
|
|
|
# in-situ manipulation
|
|
|
|
|
obj.db.test8 = [1,2,{"test":1}]
|
|
|
|
|
obj.db.test8[0] = 4
|
|
|
|
|
obj.db.test8[2]["test"] = 5
|
|
|
|
|
# test8 is now [4,2,{"test":5}]
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Retrieving Mutable objects
|
|
|
|
|
|
|
|
|
|
A side effect of the way Evennia stores Attributes is that *mutable* iterables (iterables that can
|
|
|
|
|
be modified in-place after they were created, which is everything except tuples) are handled by
|
|
|
|
|
custom objects called `_SaverList`, `_SaverDict` etc. These `_Saver...` classes behave just like the
|
|
|
|
|
normal variant except that they are aware of the database and saves to it whenever new data gets
|
|
|
|
|
assigned to them. This is what allows you to do things like `self.db.mylist[7] = val` and be sure
|
|
|
|
|
that the new version of list is saved. Without this you would have to load the list into a temporary
|
|
|
|
|
variable, change it and then re-assign it to the Attribute in order for it to save.
|
|
|
|
|
|
|
|
|
|
There is however an important thing to remember. If you retrieve your mutable iterable into another
|
|
|
|
|
variable, e.g. `mylist2 = obj.db.mylist`, your new variable (`mylist2`) will *still* be a
|
|
|
|
|
`_SaverList`. This means it will continue to save itself to the database whenever it is updated!
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
obj.db.mylist = [1,2,3,4]
|
|
|
|
|
mylist = obj.db.mylist
|
|
|
|
|
mylist[3] = 5 # this will also update database
|
|
|
|
|
print(mylist) # this is now [1,2,3,5]
|
|
|
|
|
print(obj.db.mylist) # this is also [1,2,3,5]
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
To "disconnect" your extracted mutable variable from the database you simply need to convert the
|
|
|
|
|
`_Saver...` iterable to a normal Python structure. So to convert a `_SaverList`, you use the
|
|
|
|
|
`list()` function, for a `_SaverDict` you use `dict()` and so on.
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
obj.db.mylist = [1,2,3,4]
|
|
|
|
|
mylist = list(obj.db.mylist) # convert to normal list
|
|
|
|
|
mylist[3] = 5
|
|
|
|
|
print(mylist) # this is now [1,2,3,5]
|
|
|
|
|
print(obj.db.mylist) # this is still [1,2,3,4]
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
A further problem comes with *nested mutables*, like a dict containing lists of dicts or something
|
|
|
|
|
like that. Each of these nested mutables would be `_Saver*` structures connected to the database and
|
|
|
|
|
disconnecting the outermost one of them would not disconnect those nested within. To make really
|
|
|
|
|
sure you disonnect a nested structure entirely from the database, Evennia provides a special
|
|
|
|
|
function `evennia.utils.dbserialize.deserialize`:
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
from evennia.utils.dbserialize import deserialize
|
|
|
|
|
|
|
|
|
|
decoupled_mutables = deserialize(nested_mutables)
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
The result of this operation will be a structure only consisting of normal Python mutables (`list`
|
|
|
|
|
instead of `_SaverList` and so on).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Remember, this is only valid for *mutable* iterables.
|
2021-06-23 20:05:25 +12:00
|
|
|
[Immutable](https://en.wikipedia.org/wiki/Immutable) objects (strings, numbers, tuples etc) are
|
2020-04-07 23:13:24 +02:00
|
|
|
already disconnected from the database from the onset.
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
obj.db.mytup = (1,2,[3,4])
|
|
|
|
|
obj.db.mytup[0] = 5 # this fails since tuples are immutable
|
|
|
|
|
|
|
|
|
|
# this works but will NOT update database since outermost is a tuple
|
|
|
|
|
obj.db.mytup[2][1] = 5
|
|
|
|
|
print(obj.db.mytup[2][1]) # this still returns 4, not 5
|
|
|
|
|
|
|
|
|
|
mytup1 = obj.db.mytup # mytup1 is already disconnected from database since outermost
|
|
|
|
|
# iterable is a tuple, so we can edit the internal list as we want
|
|
|
|
|
# without affecting the database.
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
> Attributes will fetch data fresh from the database whenever you read them, so
|
|
|
|
|
> if you are performing big operations on a mutable Attribute property (such as looping over a list
|
|
|
|
|
> or dict) you should make sure to "disconnect" the Attribute's value first and operate on this
|
|
|
|
|
> rather than on the Attribute. You can gain dramatic speed improvements to big loops this
|
|
|
|
|
> way.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## Locking and checking Attributes
|
|
|
|
|
|
|
|
|
|
Attributes are normally not locked down by default, but you can easily change that for individual
|
|
|
|
|
Attributes (like those that may be game-sensitive in games with user-level building).
|
|
|
|
|
|
2021-10-21 21:04:14 +02:00
|
|
|
First you need to set a *lock string* on your Attribute. Lock strings are specified [Locks](./Locks.md).
|
2020-06-16 16:53:35 +02:00
|
|
|
The relevant lock types are
|
2020-04-07 23:13:24 +02:00
|
|
|
|
|
|
|
|
- `attrread` - limits who may read the value of the Attribute
|
|
|
|
|
- `attredit` - limits who may set/change this Attribute
|
|
|
|
|
|
|
|
|
|
You cannot use the `db` handler to modify Attribute object (such as setting a lock on them) - The
|
|
|
|
|
`db` handler will return the Attribute's *value*, not the Attribute object itself. Instead you use
|
|
|
|
|
the AttributeHandler and set it to return the object instead of the value:
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
lockstring = "attread:all();attredit:perm(Admins)"
|
|
|
|
|
obj.attributes.get("myattr", return_obj=True).locks.add(lockstring)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Note the `return_obj` keyword which makes sure to return the `Attribute` object so its LockHandler
|
|
|
|
|
could be accessed.
|
|
|
|
|
|
|
|
|
|
A lock is no good if nothing checks it -- and by default Evennia does not check locks on Attributes.
|
|
|
|
|
You have to add a check to your commands/code wherever it fits (such as before setting an
|
|
|
|
|
Attribute).
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
# in some command code where we want to limit
|
|
|
|
|
# setting of a given attribute name on an object
|
|
|
|
|
attr = obj.attributes.get(attrname,
|
|
|
|
|
return_obj=True,
|
|
|
|
|
accessing_obj=caller,
|
|
|
|
|
default=None,
|
|
|
|
|
default_access=False)
|
|
|
|
|
if not attr:
|
|
|
|
|
caller.msg("You cannot edit that Attribute!")
|
|
|
|
|
return
|
|
|
|
|
# edit the Attribute here
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
The same keywords are available to use with `obj.attributes.set()` and `obj.attributes.remove()`,
|
2021-06-23 20:05:25 +12:00
|
|
|
those will check for the `attredit` lock type.
|