Update changelog with pickle improvement; update Attribute docs

This commit is contained in:
Griatch 2022-06-01 22:08:37 +02:00
parent 825d5d49e7
commit d9cd9e59f3
2 changed files with 142 additions and 95 deletions

View file

@ -162,6 +162,8 @@ Up requirements to Django 4.0+, Twisted 22+, Python 3.9 or 3.10
way to override features on all ObjectDB-inheriting objects easily. way to override features on all ObjectDB-inheriting objects easily.
- Add `TagProperty`, `AliasProperty` and `PermissionProperty` to assign these - Add `TagProperty`, `AliasProperty` and `PermissionProperty` to assign these
data in a similar way to django fields. data in a similar way to django fields.
- The db pickle-serializer now checks for methods `__serialize_dbobjs__` and `__deserialize_dbobjs__`
to allow custom packing/unpacking of nested dbobjs, to allow storing in Attribute.
## Evennia 0.9.5 ## Evennia 0.9.5

View file

@ -3,7 +3,7 @@
```{code-block} ```{code-block}
:caption: In-game :caption: In-game
> set obj/myattr = "test" > set obj/myattr = "test"
``` ```
```{code-block} python ```{code-block} python
:caption: In-code, using the .db wrapper :caption: In-code, using the .db wrapper
obj.db.foo = [1, 2, 3, "bar"] obj.db.foo = [1, 2, 3, "bar"]
@ -16,8 +16,8 @@ value = attributes.get("myattr", category="bar")
``` ```
```{code-block} python ```{code-block} python
:caption: In-code, using `AttributeProperty` at class level :caption: In-code, using `AttributeProperty` at class level
from evennia import DefaultObject from evennia import DefaultObject
from evennia import AttributeProperty from evennia import AttributeProperty
class MyObject(DefaultObject): class MyObject(DefaultObject):
foo = AttributeProperty(default=[1, 2, 3, "bar"]) foo = AttributeProperty(default=[1, 2, 3, "bar"])
@ -25,20 +25,20 @@ class MyObject(DefaultObject):
``` ```
_Attributes_ allow you to to store arbitrary data on objects and make sure the data survives a server reboot. An Attribute can store pretty much any _Attributes_ allow you to to store arbitrary data on objects and make sure the data survives a server reboot. An Attribute can store pretty much any
Python data structure and data type, like numbers, strings, lists, dicts etc. You can also Python data structure and data type, like numbers, strings, lists, dicts etc. You can also
store (references to) database objects like characters and rooms. store (references to) database objects like characters and rooms.
- [What can be stored in an Attribute](#what-types-of-data-can-i-save-in-an-attribute) is a must-read to avoid being surprised, also for experienced developers. Attributes can store _almost_ everything - [What can be stored in an Attribute](#what-types-of-data-can-i-save-in-an-attribute) is a must-read to avoid being surprised, also for experienced developers. Attributes can store _almost_ everything
but you need to know the quirks. but you need to know the quirks.
- [NAttributes](#in-memory-attributes-nattributes) are the in-memory, non-persistent - [NAttributes](#in-memory-attributes-nattributes) are the in-memory, non-persistent
siblings of Attributes. siblings of Attributes.
- [Managing Attributes In-game](#managing-attributes-in-game) for in-game builder commands. - [Managing Attributes In-game](#managing-attributes-in-game) for in-game builder commands.
## Managing Attributes in Code ## Managing Attributes in Code
Attributes are usually handled in code. All [Typeclassed](./Typeclasses.md) entities Attributes are usually handled in code. All [Typeclassed](./Typeclasses.md) entities
([Accounts](./Accounts.md), [Objects](./Objects.md), [Scripts](./Scripts.md) and ([Accounts](./Accounts.md), [Objects](./Objects.md), [Scripts](./Scripts.md) and
[Channels](./Channels.md)) can (and usually do) have Attributes associated with them. There [Channels](./Channels.md)) can (and usually do) have Attributes associated with them. There
are three ways to manage Attributes, all of which can be mixed. are three ways to manage Attributes, all of which can be mixed.
@ -50,8 +50,8 @@ are three ways to manage Attributes, all of which can be mixed.
The simplest way to get/set Attributes is to use the `.db` shortcut. This allows for setting and getting Attributes that lack a _category_ (having category `None`) The simplest way to get/set Attributes is to use the `.db` shortcut. This allows for setting and getting Attributes that lack a _category_ (having category `None`)
```python ```python
import evennia import evennia
obj = evennia.create_object(key="Foo") obj = evennia.create_object(key="Foo")
@ -64,10 +64,10 @@ obj.db.self_reference = obj # stores a reference to the obj
rose = evennia.search_object(key="rose")[0] # returns a list, grab 0th element rose = evennia.search_object(key="rose")[0] # returns a list, grab 0th element
rose.db.has_thorns = True rose.db.has_thorns = True
# retrieving # retrieving
val1 = obj.db.foo1 val1 = obj.db.foo1
val2 = obj.db.foo2 val2 = obj.db.foo2
weap = obj.db.weapon weap = obj.db.weapon
myself = obj.db.self_reference # retrieve reference from db, get object back myself = obj.db.self_reference # retrieve reference from db, get object back
is_ouch = rose.db.has_thorns is_ouch = rose.db.has_thorns
@ -75,25 +75,25 @@ is_ouch = rose.db.has_thorns
# this will return None, not AttributeError! # this will return None, not AttributeError!
not_found = obj.db.jiwjpowiwwerw not_found = obj.db.jiwjpowiwwerw
# returns all Attributes on the object # returns all Attributes on the object
obj.db.all obj.db.all
# delete an Attribute # delete an Attribute
del obj.db.foo2 del obj.db.foo2
``` ```
Trying to access a non-existing Attribute will never lead to an `AttributeError`. Instead Trying to access a non-existing Attribute will never lead to an `AttributeError`. Instead
you will get `None` back. The special `.db.all` will return a list of all Attributes on you will get `None` back. The special `.db.all` will return a list of all Attributes on
the object. You can replace this with your own Attribute `all` if you want, it will replace the the object. You can replace this with your own Attribute `all` if you want, it will replace the
default `all` functionality until you delete it again. default `all` functionality until you delete it again.
### Using .attributes ### Using .attributes
If you want to group your Attribute in a category, or don't know the name of the Attribute beforehand, you can make use of If you want to group your Attribute in a category, or don't know the name of the Attribute beforehand, you can make use of
the [AttributeHandler](evennia.typeclasses.attributes.AttributeHandler), available as `.attributes` on all typeclassed entities. With no extra keywords, this is identical to using the `.db` shortcut (`.db` is actually using the `AttributeHandler` internally): the [AttributeHandler](evennia.typeclasses.attributes.AttributeHandler), available as `.attributes` on all typeclassed entities. With no extra keywords, this is identical to using the `.db` shortcut (`.db` is actually using the `AttributeHandler` internally):
```python
is_ouch = rose.attributes.get("has_thorns")
```python
is_ouch = rose.attributes.get("has_thorns")
obj.attributes.add("helmet", "Knight's helmet") obj.attributes.add("helmet", "Knight's helmet")
helmet = obj.attributes.get("helmet") helmet = obj.attributes.get("helmet")
@ -103,7 +103,7 @@ obj.attributes.add("my game log", "long text about ...")
By using a category you can separate same-named Attributes on the same object to help organization. By using a category you can separate same-named Attributes on the same object to help organization.
```python ```python
# store (let's say we have gold_necklace and ringmail_armor from before) # store (let's say we have gold_necklace and ringmail_armor from before)
obj.attributes.add("neck", gold_necklace, category="clothing") obj.attributes.add("neck", gold_necklace, category="clothing")
obj.attributes.add("neck", ringmail_armor, category="armor") obj.attributes.add("neck", ringmail_armor, category="armor")
@ -113,19 +113,19 @@ neck_clothing = obj.attributes.get("neck", category="clothing")
neck_armor = obj.attributes.get("neck", category="armor") neck_armor = obj.attributes.get("neck", category="armor")
``` ```
If you don't specify a category, the Attribute's `category` will be `None` and can thus also be found via `.db`. `None` is considered a category of its own, so you won't find `None`-category Attributes mixed with Attributes having categories. If you don't specify a category, the Attribute's `category` will be `None` and can thus also be found via `.db`. `None` is considered a category of its own, so you won't find `None`-category Attributes mixed with Attributes having categories.
Here are the methods of the `AttributeHandler`. See Here are the methods of the `AttributeHandler`. See
the [AttributeHandler API](evennia.typeclasses.attributes.AttributeHandler) for more details. the [AttributeHandler API](evennia.typeclasses.attributes.AttributeHandler) for more details.
- `has(...)` - this checks if the object has an Attribute with this key. This is equivalent - `has(...)` - this checks if the object has an Attribute with this key. This is equivalent
to doing `obj.db.attrname` except you can also check for a specific `category. to doing `obj.db.attrname` except you can also check for a specific `category.
- `get(...)` - this retrieves the given Attribute. You can also provide a `default` value to return - `get(...)` - this retrieves the given Attribute. You can also provide a `default` value to return
if the Attribute is not defined (instead of None). By supplying an if the Attribute is not defined (instead of None). By supplying an
`accessing_object` to the call one can also make sure to check permissions before modifying `accessing_object` to the call one can also make sure to check permissions before modifying
anything. The `raise_exception` kwarg allows you to raise an `AttributeError` instead of returning anything. The `raise_exception` kwarg allows you to raise an `AttributeError` instead of returning
`None` when you access a non-existing `Attribute`. The `strattr` kwarg tells the system to store `None` when you access a non-existing `Attribute`. The `strattr` kwarg tells the system to store
the Attribute as a raw string rather than to pickle it. While an optimization this should usually the Attribute as a raw string rather than to pickle it. While an optimization this should usually
not be used unless the Attribute is used for some particular, limited purpose. not be used unless the Attribute is used for some particular, limited purpose.
- `add(...)` - this adds a new Attribute to the object. An optional [lockstring](./Locks.md) can be - `add(...)` - this adds a new Attribute to the object. An optional [lockstring](./Locks.md) can be
supplied here to restrict future access and also the call itself may be checked against locks. supplied here to restrict future access and also the call itself may be checked against locks.
@ -135,30 +135,30 @@ the [AttributeHandler API](evennia.typeclasses.attributes.AttributeHandler) for
Examples: Examples:
```python ```python
try: try:
# raise error if Attribute foo does not exist # raise error if Attribute foo does not exist
val = obj.attributes.get("foo", raise_exception=True): val = obj.attributes.get("foo", raise_exception=True):
except AttributeError: except AttributeError:
# ... # ...
# return default value if foo2 doesn't exist # return default value if foo2 doesn't exist
val2 = obj.attributes.get("foo2", default=[1, 2, 3, "bar"]) val2 = obj.attributes.get("foo2", default=[1, 2, 3, "bar"])
# delete foo if it exists (will silently fail if unset, unless # delete foo if it exists (will silently fail if unset, unless
# raise_exception is set) # raise_exception is set)
obj.attributes.remove("foo") obj.attributes.remove("foo")
# view all clothes on obj # view all clothes on obj
all_clothes = obj.attributes.all(category="clothes") all_clothes = obj.attributes.all(category="clothes")
``` ```
### Using AttributeProperty ### Using AttributeProperty
The third way to set up an Attribute is to use an `AttributeProperty`. This The third way to set up an Attribute is to use an `AttributeProperty`. This
is done on the _class level_ of your typeclass and allows you to treat Attributes a bit like Django database Fields. Unlike using `.db` and `.attributes`, an `AttributeProperty` can't be created on the fly, you must assign it in the class code. is done on the _class level_ of your typeclass and allows you to treat Attributes a bit like Django database Fields. Unlike using `.db` and `.attributes`, an `AttributeProperty` can't be created on the fly, you must assign it in the class code.
```python ```python
# mygame/typeclasses/characters.py # mygame/typeclasses/characters.py
from evennia import DefaultCharacter from evennia import DefaultCharacter
@ -173,16 +173,16 @@ class Character(DefaultCharacter):
sleepy = AttributeProperty(False, autocreate=False) sleepy = AttributeProperty(False, autocreate=False)
poisoned = AttributeProperty(False, autocreate=False) poisoned = AttributeProperty(False, autocreate=False)
def at_object_creation(self): def at_object_creation(self):
# ... # ...
``` ```
When a new instance of the class is created, new `Attributes` will be created with the value and category given. When a new instance of the class is created, new `Attributes` will be created with the value and category given.
With `AttributeProperty`'s set up like this, one can access the underlying `Attribute` like a regular property on the created object: With `AttributeProperty`'s set up like this, one can access the underlying `Attribute` like a regular property on the created object:
```python ```python
char = create_object(Character) char = create_object(Character)
char.strength # returns 10 char.strength # returns 10
@ -195,15 +195,15 @@ char.db.sleepy # returns None because autocreate=False (see below)
``` ```
```{warning} ```{warning}
Be careful to not assign AttributeProperty's to names of properties and methods already existing on the class, like 'key' or 'at_object_creation'. That could lead to very confusing errors. Be careful to not assign AttributeProperty's to names of properties and methods already existing on the class, like 'key' or 'at_object_creation'. That could lead to very confusing errors.
``` ```
The `autocreate=False` (default is `True`) used for `sleepy` and `poisoned` is worth a closer explanation. When `False`, _no_ Attribute will be auto-created for these AttributProperties unless they are _explicitly_ set. The `autocreate=False` (default is `True`) used for `sleepy` and `poisoned` is worth a closer explanation. When `False`, _no_ Attribute will be auto-created for these AttributProperties unless they are _explicitly_ set.
The advantage of not creating an Attribute is that the default value given to `AttributeProperty` is returned with no database access unless you change it. This also means that if you want to change the default later, all entities previously create will inherit the new default. The advantage of not creating an Attribute is that the default value given to `AttributeProperty` is returned with no database access unless you change it. This also means that if you want to change the default later, all entities previously create will inherit the new default.
The drawback is that without a database precense you can't find the Attribute via `.db` and `.attributes.get` (or by querying for it in other ways in the database): The drawback is that without a database precense you can't find the Attribute via `.db` and `.attributes.get` (or by querying for it in other ways in the database):
```python ```python
char.sleepy # returns False, no db access char.sleepy # returns False, no db access
char.db.sleepy # returns None - no Attribute exists char.db.sleepy # returns None - no Attribute exists
@ -217,39 +217,39 @@ char.sleepy # now returns True, involves db access
``` ```
You can e.g. `del char.strength` to set the value back to the default (the value defined You can e.g. `del char.strength` to set the value back to the default (the value defined
in the `AttributeProperty`). in the `AttributeProperty`).
See the [AttributeProperty API](evennia.typeclasses.attributes.AttributeProperty) for more details on how to create it with special options, like giving access-restrictions. See the [AttributeProperty API](evennia.typeclasses.attributes.AttributeProperty) for more details on how to create it with special options, like giving access-restrictions.
## Managing Attributes in-game ## Managing Attributes in-game
Attributes are mainly used by code. But one can also allow the builder to use Attributes to Attributes are mainly used by code. But one can also allow the builder to use Attributes to
'turn knobs' in-game. For example a builder could want to manually tweak the "level" Attribute of an 'turn knobs' in-game. For example a builder could want to manually tweak the "level" Attribute of an
enemy NPC to lower its difficuly. enemy NPC to lower its difficuly.
When setting Attributes this way, you are severely limited in what can be stored - this is because When setting Attributes this way, you are severely limited in what can be stored - this is because
giving players (even builders) the ability to store arbitrary Python would be a severe security giving players (even builders) the ability to store arbitrary Python would be a severe security
problem. problem.
In game you can set an Attribute like this: In game you can set an Attribute like this:
set myobj/foo = "bar" set myobj/foo = "bar"
To view, do To view, do
set myobj/foo set myobj/foo
or see them together with all object-info with or see them together with all object-info with
examine myobj examine myobj
The first `set`-example will store a new Attribute `foo` on the object `myobj` and give it the The first `set`-example will store a new Attribute `foo` on the object `myobj` and give it the
value "bar". value "bar".
You can store numbers, booleans, strings, tuples, lists and dicts this way. But if You can store numbers, booleans, strings, tuples, lists and dicts this way. But if
you store a list/tuple/dict they must be proper Python structures and may _only_ contain strings you store a list/tuple/dict they must be proper Python structures and may _only_ contain strings
or numbers. If you try to insert an unsupported structure, the input will be converted to a or numbers. If you try to insert an unsupported structure, the input will be converted to a
string. string.
set myobj/mybool = True set myobj/mybool = True
@ -263,8 +263,8 @@ For the last line you'll get a warning and the value instead will be saved as a
## Locking and checking Attributes ## Locking and checking Attributes
While the `set` command is limited to builders, individual Attributes are usually not While the `set` command is limited to builders, individual Attributes are usually not
locked down. You may want to lock certain sensitive Attributes, in particular for games locked down. You may want to lock certain sensitive Attributes, in particular for games
where you allow player building. You can add such limitations by adding a [lock string](./Locks.md) where you allow player building. You can add such limitations by adding a [lock string](./Locks.md)
to your Attribute. A NAttribute have no locks. to your Attribute. A NAttribute have no locks.
@ -273,7 +273,7 @@ The relevant lock types are
- `attrread` - limits who may read the value of the Attribute - `attrread` - limits who may read the value of the Attribute
- `attredit` - limits who may set/change this Attribute - `attredit` - limits who may set/change this Attribute
You must use the `AttributeHandler` to assign the lockstring to the Attribute: You must use the `AttributeHandler` to assign the lockstring to the Attribute:
```python ```python
lockstring = "attread:all();attredit:perm(Admins)" lockstring = "attread:all();attredit:perm(Admins)"
@ -281,7 +281,7 @@ obj.attributes.add("myattr", "bar", lockstring=lockstring)"
``` ```
If you already have an Attribute and want to add a lock in-place you can do so If you already have an Attribute and want to add a lock in-place you can do so
by having the `AttributeHandler` return the `Attribute` object itself (rather than by having the `AttributeHandler` return the `Attribute` object itself (rather than
its value) and then assign the lock to it directly: its value) and then assign the lock to it directly:
```python ```python
@ -293,8 +293,8 @@ Note the `return_obj` keyword which makes sure to return the `Attribute` object
could be accessed. could be accessed.
A lock is no good if nothing checks it -- and by default Evennia does not check locks on Attributes. A lock is no good if nothing checks it -- and by default Evennia does not check locks on Attributes.
To check the `lockstring` you provided, make sure you include `accessing_obj` and set To check the `lockstring` you provided, make sure you include `accessing_obj` and set
`default_access=False` as you make a `get` call. `default_access=False` as you make a `get` call.
```python ```python
# in some command code where we want to limit # in some command code where we want to limit
@ -328,13 +328,13 @@ values into a string representation before storing it to the database. This is d
With a single object, we mean anything that is *not iterable*, like numbers, strings or custom class With a single object, we mean anything that is *not iterable*, like numbers, strings or custom class
instances without the `__iter__` method. instances without the `__iter__` method.
* You can generally store any non-iterable Python entity that can be pickled. * You can generally store any non-iterable Python entity that can be _pickled_.
* Single database objects/typeclasses can be stored, despite them normally not being possible * Single database objects/typeclasses can be stored, despite them normally not being possible
to pickle. Evennia wil convert them to an internal representation using their classname, to pickle. Evennia will convert them to an internal representation using theihr classname,
database-id and creation-date with a microsecond precision. When retrieving, the object database-id and creation-date with a microsecond precision. When retrieving, the object
instance will be re-fetched from the database using this information. instance will be re-fetched from the database using this information.
* To convert the database object, Evennia must know it's there. If you *hide* a database object * If you 'hide' a db-obj as a property on a custom class, Evennia will not be
inside a non-iterable class, you will run into errors - this is not supported! able to find it to serialize it. For that you need to help it out (see below).
```{code-block} python ```{code-block} python
:caption: Valid assignments :caption: Valid assignments
@ -345,16 +345,55 @@ obj.db.test1 = False
# a database object (will be stored as an internal representation) # a database object (will be stored as an internal representation)
obj.db.test2 = myobj obj.db.test2 = myobj
``` ```
As mentioned, Evennia will not be able to automatically serialize db-objects
'hidden' in arbitrary properties on an object. This will lead to an error
when saving the Attribute.
```{code-block} python ```{code-block} python
:caption: Invalid, 'hidden' dbobject :caption: Invalid, 'hidden' dbobject
# example of storing an invalid, "hidden" dbobject in Attribute
# example of an invalid, "hidden" dbobject
class Container: class Container:
def __init__(self, mydbobj): def __init__(self, mydbobj):
# no way for Evennia to know this is a database object! # no way for Evennia to know this is a database object!
self.mydbobj = mydbobj self.mydbobj = mydbobj
# let's assume myobj is a db-object
container = Container(myobj) container = Container(myobj)
obj.db.invalid = container # will cause error! obj.db.mydata = container # will raise error!
```
By adding two methods `__serialize_dbobjs__` and `__deserialize_dbobjs__` to the
object you want to save, you can pre-serialize and post-deserialize all 'hidden'
objects before Evennia's main serializer gets to work. Inside these methods, use Evennia's
[evennia.utils.dbserialize.dbserialize](api:evennia.utils.dbserialize.dbserialize) and
[dbunserialize](api:evennia.utils.dbserialize.dbunserialize) functions to safely
serialize the db-objects you want to store.
```{code-block} python
:caption: Fixing an invalid 'hidden' dbobj for storing in Attribute
from evennia.utils import dbserialize # important
class Container:
def __init__(self, mydbobj):
# A 'hidden' db-object
self.mydbobj = mydbobj
def __serialize_dbobjs__(self):
"""This is called before serialization and allows
us to custom-handle those 'hidden' dbobjs"""
self.mydbobj = dbserialize.dbserialize(self.mydbobj
def __deserialize_dbobjs__(self):
"""This is called after deserialization and allows you to
restore the 'hidden' dbobjs you serialized before"""
self.mydbobj = dbserialize.dbunserialize(self.mydbobj)
# let's assume myobj is a db-object
container = Container(myobj)
obj.db.mydata = container # will now work fine!
``` ```
### Storing multiple objects ### Storing multiple objects
@ -404,6 +443,12 @@ obj.db.test8[2]["test"] = 5
# test8 is now [4,2,{"test":5}] # test8 is now [4,2,{"test":5}]
``` ```
Note that if make some advanced iterable object, and store an db-object on it in
a way such that it is _not_ returned by iterating over it, you have created a
'hidden' db-object. See [the previous section](#storing-single-objects) for how
to tell Evennia how to serialize such hidden objects safely.
### Retrieving Mutable objects ### Retrieving Mutable objects
A side effect of the way Evennia stores Attributes is that *mutable* iterables (iterables that can A side effect of the way Evennia stores Attributes is that *mutable* iterables (iterables that can
@ -429,41 +474,41 @@ print(obj.db.mylist) # now also [1, 2, 3, 5]
``` ```
When you extract your mutable Attribute data into a variable like `mylist`, think of it as getting a _snapshot_ When you extract your mutable Attribute data into a variable like `mylist`, think of it as getting a _snapshot_
of the variable. If you update the snapshot, it will save to the database, but this change _will not propagate to of the variable. If you update the snapshot, it will save to the database, but this change _will not propagate to
any other snapshots you may have done previously_. any other snapshots you may have done previously_.
```python ```python
obj.db.mylist = [1, 2, 3, 4] obj.db.mylist = [1, 2, 3, 4]
mylist1 = obj.db.mylist mylist1 = obj.db.mylist
mylist2 = obj.db.mylist mylist2 = obj.db.mylist
mylist1[3] = 5 mylist1[3] = 5
print(mylist1) # this is now [1, 2, 3, 5] print(mylist1) # this is now [1, 2, 3, 5]
print(obj.db.mylist) # also updated to [1, 2, 3, 5] print(obj.db.mylist) # also updated to [1, 2, 3, 5]
print(mylist2) # still [1, 2, 3, 4] ! print(mylist2) # still [1, 2, 3, 4] !
``` ```
```{sidebar} ```{sidebar}
Remember, the complexities of this section only relate to *mutable* iterables - things you can update Remember, the complexities of this section only relate to *mutable* iterables - things you can update
in-place, like lists and dicts. [Immutable](https://en.wikipedia.org/wiki/Immutable) objects (strings, in-place, like lists and dicts. [Immutable](https://en.wikipedia.org/wiki/Immutable) objects (strings,
numbers, tuples etc) are already disconnected from the database from the onset. numbers, tuples etc) are already disconnected from the database from the onset.
``` ```
To avoid confusion with mutable Attributes, only work with one variable (snapshot) at a time and save To avoid confusion with mutable Attributes, only work with one variable (snapshot) at a time and save
back the results as needed. back the results as needed.
You can also choose to "disconnect" the Attribute entirely from the You can also choose to "disconnect" the Attribute entirely from the
database with the help of the `.deserialize()` method: database with the help of the `.deserialize()` method:
```python ```python
obj.db.mylist = [1, 2, 3, 4, {1: 2}] obj.db.mylist = [1, 2, 3, 4, {1: 2}]
mylist = obj.db.mylist.deserialize() mylist = obj.db.mylist.deserialize()
``` ```
The result of this operation will be a structure only consisting of normal Python mutables (`list` The result of this operation will be a structure only consisting of normal Python mutables (`list`
instead of `_SaverList`, `dict` instead of `_SaverDict` and so on). If you update it, you need to instead of `_SaverList`, `dict` instead of `_SaverDict` and so on). If you update it, you need to
explicitly save it back to the Attribute for it to save. explicitly save it back to the Attribute for it to save.
## Properties of Attributes ## Properties of Attributes
@ -518,7 +563,7 @@ are **non-persistent** - they will _not_ survive a server reload.
Differences between `Attributes` and `NAttributes`: Differences between `Attributes` and `NAttributes`:
- `NAttribute`s are always wiped on a server reload. - `NAttribute`s are always wiped on a server reload.
- They only exist in memory and never involve the database at all, making them faster to - They only exist in memory and never involve the database at all, making them faster to
access and edit than `Attribute`s. access and edit than `Attribute`s.
- `NAttribute`s can store _any_ Python structure (and database object) without limit. - `NAttribute`s can store _any_ Python structure (and database object) without limit.
- They can _not_ be set with the standard `set` command (but they are visible with `examine`) - They can _not_ be set with the standard `set` command (but they are visible with `examine`)
@ -526,10 +571,10 @@ Differences between `Attributes` and `NAttributes`:
There are some important reasons we recommend using `ndb` to store temporary data rather than There are some important reasons we recommend using `ndb` to store temporary data rather than
the simple alternative of just storing a variable directly on an object: the simple alternative of just storing a variable directly on an object:
- NAttributes are tracked by Evennia and will not be purged in various cache-cleanup operations - NAttributes are tracked by Evennia and will not be purged in various cache-cleanup operations
the server may do. So using them guarantees that they'll remain available at least as long as the server may do. So using them guarantees that they'll remain available at least as long as
the server lives. the server lives.
- It's a consistent style - `.db/.attributes` and `.ndb/.nattributes` makes for clean-looking code - It's a consistent style - `.db/.attributes` and `.ndb/.nattributes` makes for clean-looking code
where it's clear how long-lived (or not) your data is to be. where it's clear how long-lived (or not) your data is to be.
### Persistent vs non-persistent ### Persistent vs non-persistent
@ -557,4 +602,4 @@ useful in a few situations though.
- `NAttribute`s have no restrictions at all on what they can store, since they - `NAttribute`s have no restrictions at all on what they can store, since they
don't need to worry about being saved to the database - they work very well for temporary storage. 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 - You want to implement a fully or partly *non-persistent world*. Who are we to argue with your
grand vision! grand vision!