Test refactoring of spawner (untested)
This commit is contained in:
parent
ce602716f1
commit
dd16d97834
1 changed files with 217 additions and 206 deletions
|
|
@ -141,212 +141,6 @@ for mod in settings.PROTOTYPE_MODULES:
|
||||||
for key, prot in prots})
|
for key, prot in prots})
|
||||||
_READONLY_PROTOTYPE_MODULES.update({tup[0]: mod for tup in prots})
|
_READONLY_PROTOTYPE_MODULES.update({tup[0]: mod for tup in prots})
|
||||||
|
|
||||||
|
|
||||||
def _handle_dbref(inp):
|
|
||||||
return dbid_to_obj(inp, ObjectDB)
|
|
||||||
|
|
||||||
|
|
||||||
def _validate_prototype(key, prototype, protparents, visited):
|
|
||||||
"""
|
|
||||||
Run validation on a prototype, checking for inifinite regress.
|
|
||||||
|
|
||||||
"""
|
|
||||||
assert isinstance(prototype, dict)
|
|
||||||
if id(prototype) in visited:
|
|
||||||
raise RuntimeError("%s has infinite nesting of prototypes." % key or prototype)
|
|
||||||
visited.append(id(prototype))
|
|
||||||
protstrings = prototype.get("prototype")
|
|
||||||
if protstrings:
|
|
||||||
for protstring in make_iter(protstrings):
|
|
||||||
if key is not None and protstring == key:
|
|
||||||
raise RuntimeError("%s tries to prototype itself." % key or prototype)
|
|
||||||
protparent = protparents.get(protstring)
|
|
||||||
if not protparent:
|
|
||||||
raise RuntimeError(
|
|
||||||
"%s's prototype '%s' was not found." % (key or prototype, protstring))
|
|
||||||
_validate_prototype(protstring, protparent, protparents, visited)
|
|
||||||
|
|
||||||
|
|
||||||
def _get_prototype(dic, prot, protparents):
|
|
||||||
"""
|
|
||||||
Recursively traverse a prototype dictionary, including multiple
|
|
||||||
inheritance. Use _validate_prototype before this, we don't check
|
|
||||||
for infinite recursion here.
|
|
||||||
|
|
||||||
"""
|
|
||||||
if "prototype" in dic:
|
|
||||||
# move backwards through the inheritance
|
|
||||||
for prototype in make_iter(dic["prototype"]):
|
|
||||||
# Build the prot dictionary in reverse order, overloading
|
|
||||||
new_prot = _get_prototype(protparents.get(prototype, {}), prot, protparents)
|
|
||||||
prot.update(new_prot)
|
|
||||||
prot.update(dic)
|
|
||||||
prot.pop("prototype", None) # we don't need this anymore
|
|
||||||
return prot
|
|
||||||
|
|
||||||
|
|
||||||
def _batch_create_object(*objparams):
|
|
||||||
"""
|
|
||||||
This is a cut-down version of the create_object() function,
|
|
||||||
optimized for speed. It does NOT check and convert various input
|
|
||||||
so make sure the spawned Typeclass works before using this!
|
|
||||||
|
|
||||||
Args:
|
|
||||||
objsparams (tuple): Parameters for the respective creation/add
|
|
||||||
handlers in the following order:
|
|
||||||
- `create_kwargs` (dict): For use as new_obj = `ObjectDB(**create_kwargs)`.
|
|
||||||
- `permissions` (str): Permission string used with `new_obj.batch_add(permission)`.
|
|
||||||
- `lockstring` (str): Lockstring used with `new_obj.locks.add(lockstring)`.
|
|
||||||
- `aliases` (list): A list of alias strings for
|
|
||||||
adding with `new_object.aliases.batch_add(*aliases)`.
|
|
||||||
- `nattributes` (list): list of tuples `(key, value)` to be loop-added to
|
|
||||||
add with `new_obj.nattributes.add(*tuple)`.
|
|
||||||
- `attributes` (list): list of tuples `(key, value[,category[,lockstring]])` for
|
|
||||||
adding with `new_obj.attributes.batch_add(*attributes)`.
|
|
||||||
- `tags` (list): list of tuples `(key, category)` for adding
|
|
||||||
with `new_obj.tags.batch_add(*tags)`.
|
|
||||||
- `execs` (list): Code strings to execute together with the creation
|
|
||||||
of each object. They will be executed with `evennia` and `obj`
|
|
||||||
(the newly created object) available in the namespace. Execution
|
|
||||||
will happend after all other properties have been assigned and
|
|
||||||
is intended for calling custom handlers etc.
|
|
||||||
for the respective creation/add handlers in the following
|
|
||||||
order: (create_kwargs, permissions, locks, aliases, nattributes,
|
|
||||||
attributes, tags, execs)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
objects (list): A list of created objects
|
|
||||||
|
|
||||||
Notes:
|
|
||||||
The `exec` list will execute arbitrary python code so don't allow this to be available to
|
|
||||||
unprivileged users!
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
# bulk create all objects in one go
|
|
||||||
|
|
||||||
# unfortunately this doesn't work since bulk_create doesn't creates pks;
|
|
||||||
# the result would be duplicate objects at the next stage, so we comment
|
|
||||||
# it out for now:
|
|
||||||
# dbobjs = _ObjectDB.objects.bulk_create(dbobjs)
|
|
||||||
|
|
||||||
dbobjs = [ObjectDB(**objparam[0]) for objparam in objparams]
|
|
||||||
objs = []
|
|
||||||
for iobj, obj in enumerate(dbobjs):
|
|
||||||
# call all setup hooks on each object
|
|
||||||
objparam = objparams[iobj]
|
|
||||||
# setup
|
|
||||||
obj._createdict = {"permissions": make_iter(objparam[1]),
|
|
||||||
"locks": objparam[2],
|
|
||||||
"aliases": make_iter(objparam[3]),
|
|
||||||
"nattributes": objparam[4],
|
|
||||||
"attributes": objparam[5],
|
|
||||||
"tags": make_iter(objparam[6])}
|
|
||||||
# this triggers all hooks
|
|
||||||
obj.save()
|
|
||||||
# run eventual extra code
|
|
||||||
for code in objparam[7]:
|
|
||||||
if code:
|
|
||||||
exec(code, {}, {"evennia": evennia, "obj": obj})
|
|
||||||
objs.append(obj)
|
|
||||||
return objs
|
|
||||||
|
|
||||||
|
|
||||||
def spawn(*prototypes, **kwargs):
|
|
||||||
"""
|
|
||||||
Spawn a number of prototyped objects.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
prototypes (dict): Each argument should be a prototype
|
|
||||||
dictionary.
|
|
||||||
Kwargs:
|
|
||||||
prototype_modules (str or list): A python-path to a prototype
|
|
||||||
module, or a list of such paths. These will be used to build
|
|
||||||
the global protparents dictionary accessible by the input
|
|
||||||
prototypes. If not given, it will instead look for modules
|
|
||||||
defined by settings.PROTOTYPE_MODULES.
|
|
||||||
prototype_parents (dict): A dictionary holding a custom
|
|
||||||
prototype-parent dictionary. Will overload same-named
|
|
||||||
prototypes from prototype_modules.
|
|
||||||
return_prototypes (bool): Only return a list of the
|
|
||||||
prototype-parents (no object creation happens)
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
protparents = {}
|
|
||||||
protmodules = make_iter(kwargs.get("prototype_modules", []))
|
|
||||||
if not protmodules and hasattr(settings, "PROTOTYPE_MODULES"):
|
|
||||||
protmodules = make_iter(settings.PROTOTYPE_MODULES)
|
|
||||||
for prototype_module in protmodules:
|
|
||||||
protparents.update(dict((key, val) for key, val in
|
|
||||||
all_from_module(prototype_module).items() if isinstance(val, dict)))
|
|
||||||
# overload module's protparents with specifically given protparents
|
|
||||||
protparents.update(kwargs.get("prototype_parents", {}))
|
|
||||||
for key, prototype in protparents.items():
|
|
||||||
_validate_prototype(key, prototype, protparents, [])
|
|
||||||
|
|
||||||
if "return_prototypes" in kwargs:
|
|
||||||
# only return the parents
|
|
||||||
return copy.deepcopy(protparents)
|
|
||||||
|
|
||||||
objsparams = []
|
|
||||||
for prototype in prototypes:
|
|
||||||
|
|
||||||
_validate_prototype(None, prototype, protparents, [])
|
|
||||||
prot = _get_prototype(prototype, {}, protparents)
|
|
||||||
if not prot:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# extract the keyword args we need to create the object itself. If we get a callable,
|
|
||||||
# call that to get the value (don't catch errors)
|
|
||||||
create_kwargs = {}
|
|
||||||
keyval = prot.pop("key", "Spawned Object %06i" % randint(1, 100000))
|
|
||||||
create_kwargs["db_key"] = keyval() if callable(keyval) else keyval
|
|
||||||
|
|
||||||
locval = prot.pop("location", None)
|
|
||||||
create_kwargs["db_location"] = locval() if callable(locval) else _handle_dbref(locval)
|
|
||||||
|
|
||||||
homval = prot.pop("home", settings.DEFAULT_HOME)
|
|
||||||
create_kwargs["db_home"] = homval() if callable(homval) else _handle_dbref(homval)
|
|
||||||
|
|
||||||
destval = prot.pop("destination", None)
|
|
||||||
create_kwargs["db_destination"] = destval() if callable(destval) else _handle_dbref(destval)
|
|
||||||
|
|
||||||
typval = prot.pop("typeclass", settings.BASE_OBJECT_TYPECLASS)
|
|
||||||
create_kwargs["db_typeclass_path"] = typval() if callable(typval) else typval
|
|
||||||
|
|
||||||
# extract calls to handlers
|
|
||||||
permval = prot.pop("permissions", [])
|
|
||||||
permission_string = permval() if callable(permval) else permval
|
|
||||||
lockval = prot.pop("locks", "")
|
|
||||||
lock_string = lockval() if callable(lockval) else lockval
|
|
||||||
aliasval = prot.pop("aliases", "")
|
|
||||||
alias_string = aliasval() if callable(aliasval) else aliasval
|
|
||||||
tagval = prot.pop("tags", [])
|
|
||||||
tags = tagval() if callable(tagval) else tagval
|
|
||||||
attrval = prot.pop("attrs", [])
|
|
||||||
attributes = attrval() if callable(tagval) else attrval
|
|
||||||
|
|
||||||
exval = prot.pop("exec", "")
|
|
||||||
execs = make_iter(exval() if callable(exval) else exval)
|
|
||||||
|
|
||||||
# extract ndb assignments
|
|
||||||
nattributes = dict((key.split("_", 1)[1], value() if callable(value) else value)
|
|
||||||
for key, value in prot.items() if key.startswith("ndb_"))
|
|
||||||
|
|
||||||
# the rest are attributes
|
|
||||||
simple_attributes = [(key, value()) if callable(value) else (key, value)
|
|
||||||
for key, value in prot.items() if not key.startswith("ndb_")]
|
|
||||||
attributes = attributes + simple_attributes
|
|
||||||
attributes = [tup for tup in attributes if not tup[0] in _CREATE_OBJECT_KWARGS]
|
|
||||||
|
|
||||||
# pack for call into _batch_create_object
|
|
||||||
objsparams.append((create_kwargs, permission_string, lock_string,
|
|
||||||
alias_string, nattributes, attributes, tags, execs))
|
|
||||||
|
|
||||||
return _batch_create_object(*objsparams)
|
|
||||||
|
|
||||||
|
|
||||||
# Prototype storage mechanisms
|
# Prototype storage mechanisms
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -528,6 +322,20 @@ def search_prototype(key=None, tags=None, return_meta=True):
|
||||||
|
|
||||||
return persistent_prototypes + readonly_prototypes
|
return persistent_prototypes + readonly_prototypes
|
||||||
|
|
||||||
|
|
||||||
|
def get_protparents():
|
||||||
|
"""
|
||||||
|
Get prototype parents. These are a combination of meta-key and prototype-dict and are used when
|
||||||
|
a prototype refers to another parent-prototype.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# get all prototypes
|
||||||
|
metaprotos = search_prototype(return_meta=True)
|
||||||
|
# organize by key
|
||||||
|
return {metaproto.key: metaproto.prototype for metaproto in metaprotos}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def list_prototypes(caller, key=None, tags=None, show_non_use=False, show_non_edit=True):
|
def list_prototypes(caller, key=None, tags=None, show_non_use=False, show_non_edit=True):
|
||||||
"""
|
"""
|
||||||
Collate a list of found prototypes based on search criteria and access.
|
Collate a list of found prototypes based on search criteria and access.
|
||||||
|
|
@ -588,6 +396,209 @@ def list_prototypes(caller, key=None, tags=None, show_non_use=False, show_non_ed
|
||||||
table.reformat_column(3, width=20)
|
table.reformat_column(3, width=20)
|
||||||
return table
|
return table
|
||||||
|
|
||||||
|
# Spawner mechanism
|
||||||
|
|
||||||
|
|
||||||
|
def _handle_dbref(inp):
|
||||||
|
return dbid_to_obj(inp, ObjectDB)
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_prototype(key, prototype, protparents, visited):
|
||||||
|
"""
|
||||||
|
Run validation on a prototype, checking for inifinite regress.
|
||||||
|
|
||||||
|
"""
|
||||||
|
print("validate_prototype {}, {}, {}, {}".format(key, prototype, protparents, visited))
|
||||||
|
assert isinstance(prototype, dict)
|
||||||
|
if id(prototype) in visited:
|
||||||
|
raise RuntimeError("%s has infinite nesting of prototypes." % key or prototype)
|
||||||
|
visited.append(id(prototype))
|
||||||
|
protstrings = prototype.get("prototype")
|
||||||
|
if protstrings:
|
||||||
|
for protstring in make_iter(protstrings):
|
||||||
|
if key is not None and protstring == key:
|
||||||
|
raise RuntimeError("%s tries to prototype itself." % key or prototype)
|
||||||
|
protparent = protparents.get(protstring)
|
||||||
|
if not protparent:
|
||||||
|
raise RuntimeError(
|
||||||
|
"%s's prototype '%s' was not found." % (key or prototype, protstring))
|
||||||
|
_validate_prototype(protstring, protparent, protparents, visited)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_prototype(dic, prot, protparents):
|
||||||
|
"""
|
||||||
|
Recursively traverse a prototype dictionary, including multiple
|
||||||
|
inheritance. Use _validate_prototype before this, we don't check
|
||||||
|
for infinite recursion here.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if "prototype" in dic:
|
||||||
|
# move backwards through the inheritance
|
||||||
|
for prototype in make_iter(dic["prototype"]):
|
||||||
|
# Build the prot dictionary in reverse order, overloading
|
||||||
|
new_prot = _get_prototype(protparents.get(prototype.lower(), {}), prot, protparents)
|
||||||
|
prot.update(new_prot)
|
||||||
|
prot.update(dic)
|
||||||
|
prot.pop("prototype", None) # we don't need this anymore
|
||||||
|
return prot
|
||||||
|
|
||||||
|
|
||||||
|
def _batch_create_object(*objparams):
|
||||||
|
"""
|
||||||
|
This is a cut-down version of the create_object() function,
|
||||||
|
optimized for speed. It does NOT check and convert various input
|
||||||
|
so make sure the spawned Typeclass works before using this!
|
||||||
|
|
||||||
|
Args:
|
||||||
|
objsparams (tuple): Parameters for the respective creation/add
|
||||||
|
handlers in the following order:
|
||||||
|
- `create_kwargs` (dict): For use as new_obj = `ObjectDB(**create_kwargs)`.
|
||||||
|
- `permissions` (str): Permission string used with `new_obj.batch_add(permission)`.
|
||||||
|
- `lockstring` (str): Lockstring used with `new_obj.locks.add(lockstring)`.
|
||||||
|
- `aliases` (list): A list of alias strings for
|
||||||
|
adding with `new_object.aliases.batch_add(*aliases)`.
|
||||||
|
- `nattributes` (list): list of tuples `(key, value)` to be loop-added to
|
||||||
|
add with `new_obj.nattributes.add(*tuple)`.
|
||||||
|
- `attributes` (list): list of tuples `(key, value[,category[,lockstring]])` for
|
||||||
|
adding with `new_obj.attributes.batch_add(*attributes)`.
|
||||||
|
- `tags` (list): list of tuples `(key, category)` for adding
|
||||||
|
with `new_obj.tags.batch_add(*tags)`.
|
||||||
|
- `execs` (list): Code strings to execute together with the creation
|
||||||
|
of each object. They will be executed with `evennia` and `obj`
|
||||||
|
(the newly created object) available in the namespace. Execution
|
||||||
|
will happend after all other properties have been assigned and
|
||||||
|
is intended for calling custom handlers etc.
|
||||||
|
for the respective creation/add handlers in the following
|
||||||
|
order: (create_kwargs, permissions, locks, aliases, nattributes,
|
||||||
|
attributes, tags, execs)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
objects (list): A list of created objects
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
The `exec` list will execute arbitrary python code so don't allow this to be available to
|
||||||
|
unprivileged users!
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# bulk create all objects in one go
|
||||||
|
|
||||||
|
# unfortunately this doesn't work since bulk_create doesn't creates pks;
|
||||||
|
# the result would be duplicate objects at the next stage, so we comment
|
||||||
|
# it out for now:
|
||||||
|
# dbobjs = _ObjectDB.objects.bulk_create(dbobjs)
|
||||||
|
|
||||||
|
dbobjs = [ObjectDB(**objparam[0]) for objparam in objparams]
|
||||||
|
objs = []
|
||||||
|
for iobj, obj in enumerate(dbobjs):
|
||||||
|
# call all setup hooks on each object
|
||||||
|
objparam = objparams[iobj]
|
||||||
|
# setup
|
||||||
|
obj._createdict = {"permissions": make_iter(objparam[1]),
|
||||||
|
"locks": objparam[2],
|
||||||
|
"aliases": make_iter(objparam[3]),
|
||||||
|
"nattributes": objparam[4],
|
||||||
|
"attributes": objparam[5],
|
||||||
|
"tags": make_iter(objparam[6])}
|
||||||
|
# this triggers all hooks
|
||||||
|
obj.save()
|
||||||
|
# run eventual extra code
|
||||||
|
for code in objparam[7]:
|
||||||
|
if code:
|
||||||
|
exec(code, {}, {"evennia": evennia, "obj": obj})
|
||||||
|
objs.append(obj)
|
||||||
|
return objs
|
||||||
|
|
||||||
|
def spawn(*prototypes, **kwargs):
|
||||||
|
"""
|
||||||
|
Spawn a number of prototyped objects.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
prototypes (dict): Each argument should be a prototype
|
||||||
|
dictionary.
|
||||||
|
Kwargs:
|
||||||
|
prototype_modules (str or list): A python-path to a prototype
|
||||||
|
module, or a list of such paths. These will be used to build
|
||||||
|
the global protparents dictionary accessible by the input
|
||||||
|
prototypes. If not given, it will instead look for modules
|
||||||
|
defined by settings.PROTOTYPE_MODULES.
|
||||||
|
prototype_parents (dict): A dictionary holding a custom
|
||||||
|
prototype-parent dictionary. Will overload same-named
|
||||||
|
prototypes from prototype_modules.
|
||||||
|
return_prototypes (bool): Only return a list of the
|
||||||
|
prototype-parents (no object creation happens)
|
||||||
|
|
||||||
|
"""
|
||||||
|
# get available protparents
|
||||||
|
protparents = get_protparents()
|
||||||
|
|
||||||
|
# overload module's protparents with specifically given protparents
|
||||||
|
protparents.update(kwargs.get("prototype_parents", {}))
|
||||||
|
for key, prototype in protparents.items():
|
||||||
|
_validate_prototype(key.lower(), prototype, protparents, [])
|
||||||
|
|
||||||
|
if "return_prototypes" in kwargs:
|
||||||
|
# only return the parents
|
||||||
|
return copy.deepcopy(protparents)
|
||||||
|
|
||||||
|
objsparams = []
|
||||||
|
for prototype in prototypes:
|
||||||
|
|
||||||
|
_validate_prototype(None, prototype, protparents, [])
|
||||||
|
prot = _get_prototype(prototype, {}, protparents)
|
||||||
|
if not prot:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# extract the keyword args we need to create the object itself. If we get a callable,
|
||||||
|
# call that to get the value (don't catch errors)
|
||||||
|
create_kwargs = {}
|
||||||
|
keyval = prot.pop("key", "Spawned Object %06i" % randint(1, 100000))
|
||||||
|
create_kwargs["db_key"] = keyval() if callable(keyval) else keyval
|
||||||
|
|
||||||
|
locval = prot.pop("location", None)
|
||||||
|
create_kwargs["db_location"] = locval() if callable(locval) else _handle_dbref(locval)
|
||||||
|
|
||||||
|
homval = prot.pop("home", settings.DEFAULT_HOME)
|
||||||
|
create_kwargs["db_home"] = homval() if callable(homval) else _handle_dbref(homval)
|
||||||
|
|
||||||
|
destval = prot.pop("destination", None)
|
||||||
|
create_kwargs["db_destination"] = destval() if callable(destval) else _handle_dbref(destval)
|
||||||
|
|
||||||
|
typval = prot.pop("typeclass", settings.BASE_OBJECT_TYPECLASS)
|
||||||
|
create_kwargs["db_typeclass_path"] = typval() if callable(typval) else typval
|
||||||
|
|
||||||
|
# extract calls to handlers
|
||||||
|
permval = prot.pop("permissions", [])
|
||||||
|
permission_string = permval() if callable(permval) else permval
|
||||||
|
lockval = prot.pop("locks", "")
|
||||||
|
lock_string = lockval() if callable(lockval) else lockval
|
||||||
|
aliasval = prot.pop("aliases", "")
|
||||||
|
alias_string = aliasval() if callable(aliasval) else aliasval
|
||||||
|
tagval = prot.pop("tags", [])
|
||||||
|
tags = tagval() if callable(tagval) else tagval
|
||||||
|
attrval = prot.pop("attrs", [])
|
||||||
|
attributes = attrval() if callable(tagval) else attrval
|
||||||
|
|
||||||
|
exval = prot.pop("exec", "")
|
||||||
|
execs = make_iter(exval() if callable(exval) else exval)
|
||||||
|
|
||||||
|
# extract ndb assignments
|
||||||
|
nattributes = dict((key.split("_", 1)[1], value() if callable(value) else value)
|
||||||
|
for key, value in prot.items() if key.startswith("ndb_"))
|
||||||
|
|
||||||
|
# the rest are attributes
|
||||||
|
simple_attributes = [(key, value()) if callable(value) else (key, value)
|
||||||
|
for key, value in prot.items() if not key.startswith("ndb_")]
|
||||||
|
attributes = attributes + simple_attributes
|
||||||
|
attributes = [tup for tup in attributes if not tup[0] in _CREATE_OBJECT_KWARGS]
|
||||||
|
|
||||||
|
# pack for call into _batch_create_object
|
||||||
|
objsparams.append((create_kwargs, permission_string, lock_string,
|
||||||
|
alias_string, nattributes, attributes, tags, execs))
|
||||||
|
|
||||||
|
return _batch_create_object(*objsparams)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Testing
|
# Testing
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue