Updated ReST documentation.
This commit is contained in:
parent
36b15b4ad8
commit
a8139feb1a
37 changed files with 963 additions and 910 deletions
|
|
@ -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
|
||||
-----
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue