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. - 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 - Allow `$search` funcparser func to search tags and to accept kwargs for more
powerful searches passed into the regular search functions. 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 ## Evennia 0.9.5

View file

@ -7,29 +7,29 @@ Handling storage of prototypes, both database-based ones (DBPrototypes) and thos
import hashlib import hashlib
import time import time
from django.conf import settings from django.conf import settings
from django.db.models import Q
from django.core.paginator import Paginator from django.core.paginator import Paginator
from django.db.models import Q
from django.utils.translation import gettext as _ 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.objects.models import ObjectDB
from evennia.scripts.scripts import DefaultScript
from evennia.typeclasses.attributes import Attribute from evennia.typeclasses.attributes import Attribute
from evennia.utils import dbserialize, logger
from evennia.utils.create import create_script from evennia.utils.create import create_script
from evennia.utils.evmore import EvMore from evennia.utils.evmore import EvMore
from evennia.utils.evtable import EvTable
from evennia.utils.funcparser import FuncParser
from evennia.utils.utils import ( from evennia.utils.utils import (
all_from_module, all_from_module,
variable_from_module,
make_iter,
is_iter,
dbid_to_obj,
justify,
class_from_module, 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_PROTOTYPE_MODULES = {}
_MODULE_PROTOTYPES = {} _MODULE_PROTOTYPES = {}
@ -925,7 +925,13 @@ def validate_prototype(
def protfunc_parser( 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. 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. available_functions (dict, optional): Mapping of name:protfunction to use for this parsing.
If not set, use default sources. If not set, use default sources.
stacktrace (bool, optional): If set, print the stack parsing process of the protfunc-parser. 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: Keyword Args:
session (Session): Passed to protfunc. Session of the entity spawning the prototype. session (Session): Passed to protfunc. Session of the entity spawning the prototype.
@ -957,7 +964,7 @@ def protfunc_parser(
if not isinstance(value, str): if not isinstance(value, str):
return value 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 return result
@ -1100,7 +1107,9 @@ def check_permission(prototype_key, action, default=True):
return default 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. 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))) value = validator(value[0](*make_iter(args)))
else: else:
value = validator(value) 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: if result != value:
return validator(result) return validator(result)
return result return result

View file

@ -137,21 +137,19 @@ import copy
import hashlib import hashlib
import time import time
import evennia
from django.conf import settings from django.conf import settings
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
import evennia
from evennia.objects.models import ObjectDB 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 import prototypes as protlib
from evennia.prototypes.prototypes import ( from evennia.prototypes.prototypes import (
PROTOTYPE_TAG_CATEGORY,
init_spawn_value,
value_to_obj, value_to_obj,
value_to_obj_or_any, 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") _CREATE_OBJECT_KWARGS = ("key", "location", "home", "destination")
_PROTOTYPE_META_NAMES = ( _PROTOTYPE_META_NAMES = (
@ -634,7 +632,7 @@ def format_diff(diff, minimal=True):
def batch_update_objects_with_prototype( 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. 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 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. 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. 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: Returns:
changed (int): The number of objects that had changes applied to them. 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 do_save = True
def _init(val, typ): 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": if key == "key":
obj.db_key = _init(val, str) obj.db_key = _init(val, str)
@ -892,6 +898,8 @@ def spawn(*prototypes, caller=None, **kwargs):
custom `prototype_parents` are given to this function. custom `prototype_parents` are given to this function.
only_validate (bool): Only run validation of prototype/parents only_validate (bool): Only run validation of prototype/parents
(no object creation) and return the create-kwargs. (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: Returns:
object (Object, dict or list): Spawned object(s). If `only_validate` is given, return 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, # 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) # call that to get the value (don't catch errors)
create_kwargs = {} 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) # 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. # chance this is not unique but it should usually not be a problem.
val = prot.pop( val = prot.pop(
"key", "key",
"Spawned-{}".format(hashlib.md5(bytes(str(time.time()), "utf-8")).hexdigest()[:6]), "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) val = prot.pop("location", None)
create_kwargs["db_location"] = init_spawn_value( create_kwargs["db_location"] = init_spawn_value(val, value_to_obj, **init_spawn_kwargs)
val, value_to_obj, caller=caller, prototype=prototype
)
val = prot.pop("home", None) val = prot.pop("home", None)
if val: if val:
create_kwargs["db_home"] = init_spawn_value( create_kwargs["db_home"] = init_spawn_value(val, value_to_obj, **init_spawn_kwargs)
val, value_to_obj, caller=caller, prototype=prototype
)
else: else:
try: try:
create_kwargs["db_home"] = init_spawn_value( 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: except ObjectDB.DoesNotExist:
# settings.DEFAULT_HOME not existing is common for unittests # settings.DEFAULT_HOME not existing is common for unittests
pass pass
val = prot.pop("destination", None) val = prot.pop("destination", None)
create_kwargs["db_destination"] = init_spawn_value( create_kwargs["db_destination"] = init_spawn_value(val, value_to_obj, **init_spawn_kwargs)
val, value_to_obj, caller=caller, prototype=prototype
)
val = prot.pop("typeclass", settings.BASE_OBJECT_TYPECLASS) val = prot.pop("typeclass", settings.BASE_OBJECT_TYPECLASS)
create_kwargs["db_typeclass_path"] = init_spawn_value( create_kwargs["db_typeclass_path"] = init_spawn_value(val, str, **init_spawn_kwargs)
val, str, caller=caller, prototype=prototype
)
# extract calls to handlers # extract calls to handlers
val = prot.pop("permissions", []) 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", "") 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", []) 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", []) val = prot.pop("tags", [])
tags = [] tags = []
for (tag, category, *data) in val: for (tag, category, *data) in val:
tags.append( tags.append(
( (
init_spawn_value(tag, str, caller=caller, prototype=prototype), init_spawn_value(tag, str, **init_spawn_kwargs),
category, category,
data[0] if data else None, data[0] if data else None,
) )
@ -1000,13 +1006,13 @@ def spawn(*prototypes, caller=None, **kwargs):
tags.append((prototype_key, PROTOTYPE_TAG_CATEGORY)) tags.append((prototype_key, PROTOTYPE_TAG_CATEGORY))
val = prot.pop("exec", "") 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 # extract ndb assignments
nattributes = dict( nattributes = dict(
( (
key.split("_", 1)[1], 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() for key, val in prot.items()
if key.startswith("ndb_") if key.startswith("ndb_")
@ -1019,7 +1025,7 @@ def spawn(*prototypes, caller=None, **kwargs):
attributes.append( attributes.append(
( (
attrname, attrname,
init_spawn_value(value, caller=caller, prototype=prototype), init_spawn_value(value, **init_spawn_kwargs),
rest[0] if rest else None, rest[0] if rest else None,
rest[1] if len(rest) > 1 else None, rest[1] if len(rest) > 1 else None,
) )
@ -1036,9 +1042,7 @@ def spawn(*prototypes, caller=None, **kwargs):
simple_attributes.append( simple_attributes.append(
( (
key, key,
init_spawn_value( init_spawn_value(value, value_to_obj_or_any, **init_spawn_kwargs),
value, value_to_obj_or_any, caller=caller, prototype=prototype
),
None, None,
None, None,
) )