diff --git a/evennia/utils/spawner.py b/evennia/utils/spawner.py index 1b0bbb63a3..00cfb25627 100644 --- a/evennia/utils/spawner.py +++ b/evennia/utils/spawner.py @@ -141,212 +141,6 @@ for mod in settings.PROTOTYPE_MODULES: for key, prot 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 @@ -528,6 +322,20 @@ def search_prototype(key=None, tags=None, return_meta=True): 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): """ 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) 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