Add protfunc_raise_errors to spawn function, per #2769

This commit is contained in:
Griatch 2022-10-23 11:55:11 +02:00
parent 0ed055a0a4
commit f2f5a9d99d
3 changed files with 66 additions and 49 deletions

View file

@ -205,6 +205,8 @@ Up requirements to Django 4.0+, Twisted 22+, Python 3.9 or 3.10
- Make setting `MAX_NR_CHARACTERS` interact better with the new settings above.
- Allow `$search` funcparser func to search tags and to accept kwargs for more
powerful searches passed into the regular search functions.
- `spawner.spawn` and linked methods now has a kwarg `protfunc_raise_errors`
(default True) to disable strict errors on malformed/not-found protfuncs
## Evennia 0.9.5

View file

@ -7,29 +7,29 @@ Handling storage of prototypes, both database-based ones (DBPrototypes) and thos
import hashlib
import time
from django.conf import settings
from django.db.models import Q
from django.core.paginator import Paginator
from django.db.models import Q
from django.utils.translation import gettext as _
from evennia.scripts.scripts import DefaultScript
from evennia.locks.lockhandler import check_lockstring, validate_lockstring
from evennia.objects.models import ObjectDB
from evennia.scripts.scripts import DefaultScript
from evennia.typeclasses.attributes import Attribute
from evennia.utils import dbserialize, logger
from evennia.utils.create import create_script
from evennia.utils.evmore import EvMore
from evennia.utils.evtable import EvTable
from evennia.utils.funcparser import FuncParser
from evennia.utils.utils import (
all_from_module,
variable_from_module,
make_iter,
is_iter,
dbid_to_obj,
justify,
class_from_module,
dbid_to_obj,
is_iter,
justify,
make_iter,
variable_from_module,
)
from evennia.locks.lockhandler import validate_lockstring, check_lockstring
from evennia.utils import logger
from evennia.utils.funcparser import FuncParser
from evennia.utils import dbserialize
from evennia.utils.evtable import EvTable
_MODULE_PROTOTYPE_MODULES = {}
_MODULE_PROTOTYPES = {}
@ -925,7 +925,13 @@ def validate_prototype(
def protfunc_parser(
value, available_functions=None, testing=False, stacktrace=False, caller=None, **kwargs
value,
available_functions=None,
testing=False,
stacktrace=False,
caller=None,
raise_errors=True,
**kwargs,
):
"""
Parse a prototype value string for a protfunc and process it.
@ -939,6 +945,7 @@ def protfunc_parser(
available_functions (dict, optional): Mapping of name:protfunction to use for this parsing.
If not set, use default sources.
stacktrace (bool, optional): If set, print the stack parsing process of the protfunc-parser.
raise_errors (bool, optional): Raise explicit errors from malformed/not found protfunc calls.
Keyword Args:
session (Session): Passed to protfunc. Session of the entity spawning the prototype.
@ -957,7 +964,7 @@ def protfunc_parser(
if not isinstance(value, str):
return value
result = FUNC_PARSER.parse_to_any(value, raise_errors=True, caller=caller, **kwargs)
result = FUNC_PARSER.parse_to_any(value, raise_errors=raise_errors, caller=caller, **kwargs)
return result
@ -1100,7 +1107,9 @@ def check_permission(prototype_key, action, default=True):
return default
def init_spawn_value(value, validator=None, caller=None, prototype=None):
def init_spawn_value(
value, validator=None, caller=None, prototype=None, protfunc_raise_errors=True
):
"""
Analyze the prototype value and produce a value useful at the point of spawning.
@ -1128,7 +1137,9 @@ def init_spawn_value(value, validator=None, caller=None, prototype=None):
value = validator(value[0](*make_iter(args)))
else:
value = validator(value)
result = protfunc_parser(value, caller=caller, prototype=prototype)
result = protfunc_parser(
value, caller=caller, prototype=prototype, raise_errors=protfunc_raise_errors
)
if result != value:
return validator(result)
return result

View file

@ -137,21 +137,19 @@ import copy
import hashlib
import time
import evennia
from django.conf import settings
from django.utils.translation import gettext as _
import evennia
from evennia.objects.models import ObjectDB
from evennia.utils import logger
from evennia.utils.utils import make_iter, is_iter
from evennia.prototypes import prototypes as protlib
from evennia.prototypes.prototypes import (
PROTOTYPE_TAG_CATEGORY,
init_spawn_value,
value_to_obj,
value_to_obj_or_any,
init_spawn_value,
PROTOTYPE_TAG_CATEGORY,
)
from evennia.utils import logger
from evennia.utils.utils import is_iter, make_iter
_CREATE_OBJECT_KWARGS = ("key", "location", "home", "destination")
_PROTOTYPE_META_NAMES = (
@ -634,7 +632,7 @@ def format_diff(diff, minimal=True):
def batch_update_objects_with_prototype(
prototype, diff=None, objects=None, exact=False, caller=None
prototype, diff=None, objects=None, exact=False, caller=None, protfunc_raise_errors=True
):
"""
Update existing objects with the latest version of the prototype.
@ -653,6 +651,8 @@ def batch_update_objects_with_prototype(
objects will be removed if they exist. This will lead to a more accurate 1:1 correlation
between the object and the prototype but is usually impractical.
caller (Object or Account, optional): This may be used by protfuncs to do permission checks.
protfunc_raise_errors (bool): Have protfuncs raise explicit errors if malformed/not found.
This is highly recommended.
Returns:
changed (int): The number of objects that had changes applied to them.
@ -704,7 +704,13 @@ def batch_update_objects_with_prototype(
do_save = True
def _init(val, typ):
return init_spawn_value(val, str, caller=caller, prototype=new_prototype)
return init_spawn_value(
val,
str,
caller=caller,
prototype=new_prototype,
protfunc_raise_errors=protfunc_raise_errors,
)
if key == "key":
obj.db_key = _init(val, str)
@ -892,6 +898,8 @@ def spawn(*prototypes, caller=None, **kwargs):
custom `prototype_parents` are given to this function.
only_validate (bool): Only run validation of prototype/parents
(no object creation) and return the create-kwargs.
protfunc_raise_errors (bool): Raise explicit exceptions on a malformed/not-found
protfunc. Defaults to True.
Returns:
object (Object, dict or list): Spawned object(s). If `only_validate` is given, return
@ -938,57 +946,55 @@ def spawn(*prototypes, caller=None, **kwargs):
# 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 = {}
init_spawn_kwargs = dict(
caller=caller,
prototype=prototype,
protfunc_raise_errors=kwargs.get("protfunc_raise_errors", True),
)
# we must always add a key, so if not given we use a shortened md5 hash. There is a (small)
# chance this is not unique but it should usually not be a problem.
val = prot.pop(
"key",
"Spawned-{}".format(hashlib.md5(bytes(str(time.time()), "utf-8")).hexdigest()[:6]),
)
create_kwargs["db_key"] = init_spawn_value(val, str, caller=caller, prototype=prototype)
create_kwargs["db_key"] = init_spawn_value(val, str, **init_spawn_kwargs)
val = prot.pop("location", None)
create_kwargs["db_location"] = init_spawn_value(
val, value_to_obj, caller=caller, prototype=prototype
)
create_kwargs["db_location"] = init_spawn_value(val, value_to_obj, **init_spawn_kwargs)
val = prot.pop("home", None)
if val:
create_kwargs["db_home"] = init_spawn_value(
val, value_to_obj, caller=caller, prototype=prototype
)
create_kwargs["db_home"] = init_spawn_value(val, value_to_obj, **init_spawn_kwargs)
else:
try:
create_kwargs["db_home"] = init_spawn_value(
settings.DEFAULT_HOME, value_to_obj, caller=caller, prototype=prototype
settings.DEFAULT_HOME, value_to_obj, **init_spawn_kwargs
)
except ObjectDB.DoesNotExist:
# settings.DEFAULT_HOME not existing is common for unittests
pass
val = prot.pop("destination", None)
create_kwargs["db_destination"] = init_spawn_value(
val, value_to_obj, caller=caller, prototype=prototype
)
create_kwargs["db_destination"] = init_spawn_value(val, value_to_obj, **init_spawn_kwargs)
val = prot.pop("typeclass", settings.BASE_OBJECT_TYPECLASS)
create_kwargs["db_typeclass_path"] = init_spawn_value(
val, str, caller=caller, prototype=prototype
)
create_kwargs["db_typeclass_path"] = init_spawn_value(val, str, **init_spawn_kwargs)
# extract calls to handlers
val = prot.pop("permissions", [])
permission_string = init_spawn_value(val, make_iter, caller=caller, prototype=prototype)
permission_string = init_spawn_value(val, make_iter, **init_spawn_kwargs)
val = prot.pop("locks", "")
lock_string = init_spawn_value(val, str, caller=caller, prototype=prototype)
lock_string = init_spawn_value(val, str, **init_spawn_kwargs)
val = prot.pop("aliases", [])
alias_string = init_spawn_value(val, make_iter, caller=caller, prototype=prototype)
alias_string = init_spawn_value(val, make_iter, **init_spawn_kwargs)
val = prot.pop("tags", [])
tags = []
for (tag, category, *data) in val:
tags.append(
(
init_spawn_value(tag, str, caller=caller, prototype=prototype),
init_spawn_value(tag, str, **init_spawn_kwargs),
category,
data[0] if data else None,
)
@ -1000,13 +1006,13 @@ def spawn(*prototypes, caller=None, **kwargs):
tags.append((prototype_key, PROTOTYPE_TAG_CATEGORY))
val = prot.pop("exec", "")
execs = init_spawn_value(val, make_iter, caller=caller, prototype=prototype)
execs = init_spawn_value(val, make_iter, **init_spawn_kwargs)
# extract ndb assignments
nattributes = dict(
(
key.split("_", 1)[1],
init_spawn_value(val, value_to_obj, caller=caller, prototype=prototype),
init_spawn_value(val, value_to_obj, **init_spawn_kwargs),
)
for key, val in prot.items()
if key.startswith("ndb_")
@ -1019,7 +1025,7 @@ def spawn(*prototypes, caller=None, **kwargs):
attributes.append(
(
attrname,
init_spawn_value(value, caller=caller, prototype=prototype),
init_spawn_value(value, **init_spawn_kwargs),
rest[0] if rest else None,
rest[1] if len(rest) > 1 else None,
)
@ -1036,9 +1042,7 @@ def spawn(*prototypes, caller=None, **kwargs):
simple_attributes.append(
(
key,
init_spawn_value(
value, value_to_obj_or_any, caller=caller, prototype=prototype
),
init_spawn_value(value, value_to_obj_or_any, **init_spawn_kwargs),
None,
None,
)