Updated ReST documentation.

This commit is contained in:
Griatch 2012-05-01 17:37:37 +02:00
parent 36b15b4ad8
commit a8139feb1a
37 changed files with 963 additions and 910 deletions

View file

@ -24,25 +24,25 @@ Saving and Retrieving data
--------------------------
To save persistent data on a Typeclassed object you normally use the
``db`` operator. Let's try to save some data to a *Rose* (an
``db`` (!DataBase) operator. Let's try to save some data to a *Rose* (an
`Object <Objects.html>`_):
::
# saving rose.db.has_thorns = True # getting it back is_ouch = rose.db.has_thorns
# 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 create a
database entry, you use ``ndb`` (!NonDataBase). It works in the same
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:
::
# saving rose.ndb.has_thorns = True # getting it back is_ouch = rose.ndb.has_thorns
# saving rose.ndb.has_thorns = True # getting it back is_ouch = rose.ndb.has_thorns
Strictly speaking, ``ndb`` has nothing to do with ``Attributes``,
despite how similar they look. No ``Attribute`` object is created behind
@ -56,28 +56,52 @@ will for example delete an ``Attribute``:
del rose.db.has_thorns
Fast assignment
---------------
For quick testing you can most often skip the ``db`` operator and assign
Attributes like you would any normal Python property:
Both ``db`` and ``ndb`` defaults to offering an ``all`` property on
themselves. This returns all associated attributes or non-persistent
properties.
::
# saving rose.has_thorns = True# getting it back is_ouch = rose.has_thorns
list_of_all_rose_attributes = rose.db.all list_of_all_rose_ndb_attrs = rose.ndb.all
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.
Fast assignment
---------------
For quick testing you can in principle skip the ``db`` operator and
assign Attributes like you would any normal Python property:
::
# saving rose.has_thorns = True # getting it back is_ouch = rose.has_thorns
This looks like any normal Python assignment, but calls ``db`` behind
the scenes for you.
Note however that this form stands the chance of overloading already
existing properties on typeclasses and their database objects.
``rose.msg()`` is for example an already defined method for sending
messages. Doing ``rose.msg = "Ouch"`` will overload the method with a
string and will create all sorts of trouble down the road (the engine
uses ``msg()`` a lot to send text to you). Using
``rose.db.msg = "Ouch"`` will always do what you expect and is usually
the safer bet. And it also makes it visually clear at all times when you
are saving to the database and not.
existing properties on typeclasses and their database objects. Unless
you know what you are doing, this can cause lots of trouble.
::
rose.msg("hello") # this uses the in-built msg() method rose.msg = "Ouch!" # this OVERLOADS the msg() method with a string rose.msg("hello") # this now a gives traceback!
Overloading ``msg()`` with a string is a very bad idea since Evennia
uses this method all the time to send text to you. There are of course
situations when you *want* to overload default methods with your own
implementations - but then you'll hopefully do so intentionally and with
something that works.
::
rose.db.msg = "Ouch" # this stands no risk of overloading msg() rose.msg("hello") # this works as it should
So using ``db``/``ndb`` will always do what you expect and is usually
the safer bet. It also makes it visually clear at all times when you are
saving to the database and not.
Another drawback of this shorter form is that it will handle a non-found
Attribute as it would any non-found property on the object. The ``db``
@ -97,32 +121,33 @@ 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. Maybe you are
reading/storing data many times a second (for whatever reason) or you
have many players doing things at the same time. Hitting the database
over and over might not be ideal in that case. Non-persistent data
simply writes to memory, it doesn't hit the database at all. It
should be said that with the speed and quality of hardware these
days, this point is less likely to be of any big concern except for
the most extreme of situations. Modern database systems cache data
very efficiently for speed. Our default database even runs completely
in RAM if possible, alleviating much of the need to write to disk
during heavy loads.
- You *want* to loose your state when logging off. Maybe you are
testing a buggy `Script <Scripts.html>`_ that does potentially
harmful stuff to your character object. With non-persistent storage
you can be sure that whatever the script messes up, it's nothing a
server reboot can't clear up.
- 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 caching, modern
database systems themselves also cache data very efficiently for
speed. Our default 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
loose your state when logging off. Maybe you are storing throw-away
data that are re-initialized at server startup. Maybe you are
implementing some caching of your own. Or maybe you are testing a
buggy `Script <Scripts.html>`_ that does potentially harmful stuff to
your character object. With non-persistent storage you can be sure
that whatever is messed up, it's nothing a server reboot can't clear
up.
- 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?
------------------------------
What types of data can I save in an Attribute?
----------------------------------------------
If you store a single object (that is, not a iterable list of objects),
If you store a single object (that is, not an iterable list of objects),
you can practically store any Python object that can be
`pickled <http://docs.python.org/library/pickle.html>`_. Evennia uses
the ``pickle`` module to serialize data into the database.
the ``pickle`` module to serialize Attribute data into the database.
There is one notable type of object that cannot be pickled - and that is
a Django database object. These will instead be stored as a wrapper
@ -132,7 +157,7 @@ accessed. Since erroneously trying to save database objects in an
Attribute will lead to errors, Evennia will try to detect database
objects by analyzing the data being stored. This means that Evennia must
recursively traverse all iterables to make sure all database objects in
them are stored safely. So for efficiency, it can be a good idea is to
them are stored safely. So for efficiency, it can be a good idea to
avoid deeply nested lists with objects if you can.
To store several objects, you may only use python *lists*,
@ -146,13 +171,14 @@ usually not a limitation you need to worry about.
custom, non-iterable classes and stored database objects in them. So to
make this clear - saving such an object is **not supported** and will
probably make your game unstable. Store your database objects using
lists, dictionaries or a combination of the two and you should be fine.*
lists, tuples, dictionaries or a combination of the three and you should
be fine.*
Examples of valid attribute data:
::
# a single value obj.db.test1 = 23 obj.db.test1 = False # a database object (will be stored as dbref) obj.db.test2 = myobj # a list of objects 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 will still be stored and returned as a list [1,2,3,4,5]! obj.db.test7 = set([1,2,3,4,5])
# a single value obj.db.test1 = 23 obj.db.test1 = False # a database object (will be stored as dbref) obj.db.test2 = myobj # a list of objects 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 will still be stored and returned as a list [1,2,3,4,5]! 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]
Example of non-supported save:
@ -164,21 +190,44 @@ Retrieving Mutable objects
--------------------------
A side effect of the way Evennia stores Attributes is that Python Lists
and Dictionaries (only )are handled by custom objects called PackedLists
and !PackedDicts. These have the special property that they save to the
database whenever new data gets assigned to them. This allows you to do
things like self.db.mylist`4 <4.html>`_
val without having to extract the mylist Attribute into a temporary
variable first.
and Dictionaries (only) are handled by custom objects called PackedLists
and !PackedDicts. These behave just like normal lists and dicts except
they have the special property that they save to the database whenever
new data gets assigned to them. This allows you to do things like
``self.db.mylist[4]`` = val without having to extract the mylist
Attribute into a temporary variable first.
There is however an important thing to remember. If you retrieve this
data into another variable, e.g. ``mylist2 = obj.db.mylist``, your new
variable will *still* be a PackedList, and if you assign things to it,
it will save to the database! To "disconnect" it from the database
system, you need to convert it to a normal list with mylist2
variable (``mylist2``) will *still* be a !PackedList! This means it will
continue to save itself to the database whenever it is updated! This is
important to keep in mind so you are not confused by the results.
list(mylist2).
::
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 mylist.db.mylist # this is also [1,2,3,5]
To "disconnect" your extracted mutable variable from the database you
simply need to convert the PackedList or PackedDict to a normal Python
list or dictionary. This is done with the builtin ``list()`` and
``dict()`` functions. In the case of "nested" lists and dicts, you only
have to convert the "outermost" list/dict in order to cut the entire
structure's connection to the database.
::
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 remains [1,2,3,4]
Remember, this is only valid for mutable iterables - lists and dicts and
combinations of the two.
`Immutable <http://en.wikipedia.org/wiki/Immutable>`_ objects (strings,
numbers, tuples etc) are already disconnected from the database from the
onset. So making the outermost iterable into a tuple is also a way to
stop any changes to the structure from updating the database.
::
obj.db.mytup = (1,2,[3,4]) obj.db.mytup[0] = 5 # this fails since tuples are immutable obj.db.mytup[2][1] = 5 # this works but will NOT update database since outermost iterable is a tuple 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.
Notes
-----