Continued refactoring
This commit is contained in:
parent
054cba42bf
commit
1a8651f18b
3 changed files with 295 additions and 347 deletions
|
|
@ -61,6 +61,7 @@ class DbPrototype(DefaultScript):
|
||||||
|
|
||||||
# General prototype functions
|
# General prototype functions
|
||||||
|
|
||||||
|
|
||||||
def check_permission(prototype_key, action, default=True):
|
def check_permission(prototype_key, action, default=True):
|
||||||
"""
|
"""
|
||||||
Helper function to check access to actions on given prototype.
|
Helper function to check access to actions on given prototype.
|
||||||
|
|
@ -278,3 +279,276 @@ def search_objects_with_prototype(prototype_key):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return ObjectDB.objects.get_by_tag(key=prototype_key, category=_PROTOTYPE_TAG_CATEGORY)
|
return ObjectDB.objects.get_by_tag(key=prototype_key, category=_PROTOTYPE_TAG_CATEGORY)
|
||||||
|
|
||||||
|
|
||||||
|
def prototype_from_object(obj):
|
||||||
|
"""
|
||||||
|
Guess a minimal prototype from an existing object.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
obj (Object): An object to analyze.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
prototype (dict): A prototype estimating the current state of the object.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# first, check if this object already has a prototype
|
||||||
|
|
||||||
|
prot = obj.tags.get(category=_PROTOTYPE_TAG_CATEGORY, return_list=True)
|
||||||
|
prot = search_prototype(prot)
|
||||||
|
if not prot or len(prot) > 1:
|
||||||
|
# no unambiguous prototype found - build new prototype
|
||||||
|
prot = {}
|
||||||
|
prot['prototype_key'] = "From-Object-{}-{}".format(
|
||||||
|
obj.key, hashlib.md5(str(time.time())).hexdigest()[:6])
|
||||||
|
prot['prototype_desc'] = "Built from {}".format(str(obj))
|
||||||
|
prot['prototype_locks'] = "spawn:all();edit:all()"
|
||||||
|
|
||||||
|
prot['key'] = obj.db_key or hashlib.md5(str(time.time())).hexdigest()[:6]
|
||||||
|
prot['location'] = obj.db_location
|
||||||
|
prot['home'] = obj.db_home
|
||||||
|
prot['destination'] = obj.db_destination
|
||||||
|
prot['typeclass'] = obj.db_typeclass_path
|
||||||
|
prot['locks'] = obj.locks.all()
|
||||||
|
prot['permissions'] = obj.permissions.get()
|
||||||
|
prot['aliases'] = obj.aliases.get()
|
||||||
|
prot['tags'] = [(tag.key, tag.category, tag.data)
|
||||||
|
for tag in obj.tags.get(return_tagobj=True, return_list=True)]
|
||||||
|
prot['attrs'] = [(attr.key, attr.value, attr.category, attr.locks)
|
||||||
|
for attr in obj.attributes.get(return_obj=True, return_list=True)]
|
||||||
|
|
||||||
|
return prot
|
||||||
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
caller (Account or Object): The object requesting the list.
|
||||||
|
key (str, optional): Exact or partial prototype key to query for.
|
||||||
|
tags (str or list, optional): Tag key or keys to query for.
|
||||||
|
show_non_use (bool, optional): Show also prototypes the caller may not use.
|
||||||
|
show_non_edit (bool, optional): Show also prototypes the caller may not edit.
|
||||||
|
Returns:
|
||||||
|
table (EvTable or None): An EvTable representation of the prototypes. None
|
||||||
|
if no prototypes were found.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# this allows us to pass lists of empty strings
|
||||||
|
tags = [tag for tag in make_iter(tags) if tag]
|
||||||
|
|
||||||
|
# get prototypes for readonly and db-based prototypes
|
||||||
|
prototypes = search_prototype(key, tags)
|
||||||
|
|
||||||
|
# get use-permissions of readonly attributes (edit is always False)
|
||||||
|
display_tuples = []
|
||||||
|
for prototype in sorted(prototypes, key=lambda d: d.get('prototype_key', '')):
|
||||||
|
lock_use = caller.locks.check_lockstring(
|
||||||
|
caller, prototype.get('prototype_locks', ''), access_type='spawn')
|
||||||
|
if not show_non_use and not lock_use:
|
||||||
|
continue
|
||||||
|
if prototype.get('prototype_key', '') in _MODULE_PROTOTYPES:
|
||||||
|
lock_edit = False
|
||||||
|
else:
|
||||||
|
lock_edit = caller.locks.check_lockstring(
|
||||||
|
caller, prototype.get('prototype_locks', ''), access_type='edit')
|
||||||
|
if not show_non_edit and not lock_edit:
|
||||||
|
continue
|
||||||
|
ptags = []
|
||||||
|
for ptag in prototype.get('prototype_tags', []):
|
||||||
|
if is_iter(ptag):
|
||||||
|
if len(ptag) > 1:
|
||||||
|
ptags.append("{} (category: {}".format(ptag[0], ptag[1]))
|
||||||
|
else:
|
||||||
|
ptags.append(ptag[0])
|
||||||
|
else:
|
||||||
|
ptags.append(str(ptag))
|
||||||
|
|
||||||
|
display_tuples.append(
|
||||||
|
(prototype.get('prototype_key', '<unset>'),
|
||||||
|
prototype.get('prototype_desc', '<unset>'),
|
||||||
|
"{}/{}".format('Y' if lock_use else 'N', 'Y' if lock_edit else 'N'),
|
||||||
|
",".join(ptags)))
|
||||||
|
|
||||||
|
if not display_tuples:
|
||||||
|
return None
|
||||||
|
|
||||||
|
table = []
|
||||||
|
width = 78
|
||||||
|
for i in range(len(display_tuples[0])):
|
||||||
|
table.append([str(display_tuple[i]) for display_tuple in display_tuples])
|
||||||
|
table = EvTable("Key", "Desc", "Spawn/Edit", "Tags", table=table, crop=True, width=width)
|
||||||
|
table.reformat_column(0, width=22)
|
||||||
|
table.reformat_column(1, width=29)
|
||||||
|
table.reformat_column(2, width=11, align='c')
|
||||||
|
table.reformat_column(3, width=16)
|
||||||
|
return table
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def batch_update_objects_with_prototype(prototype, diff=None, objects=None):
|
||||||
|
"""
|
||||||
|
Update existing objects with the latest version of the prototype.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
prototype (str or dict): Either the `prototype_key` to use or the
|
||||||
|
prototype dict itself.
|
||||||
|
diff (dict, optional): This a diff structure that describes how to update the protototype.
|
||||||
|
If not given this will be constructed from the first object found.
|
||||||
|
objects (list, optional): List of objects to update. If not given, query for these
|
||||||
|
objects using the prototype's `prototype_key`.
|
||||||
|
Returns:
|
||||||
|
changed (int): The number of objects that had changes applied to them.
|
||||||
|
|
||||||
|
"""
|
||||||
|
prototype_key = prototype if isinstance(prototype, basestring) else prototype['prototype_key']
|
||||||
|
prototype_obj = search_db_prototype(prototype_key, return_queryset=True)
|
||||||
|
prototype_obj = prototype_obj[0] if prototype_obj else None
|
||||||
|
new_prototype = prototype_obj.db.prototype
|
||||||
|
objs = ObjectDB.objects.get_by_tag(prototype_key, category=_PROTOTYPE_TAG_CATEGORY)
|
||||||
|
|
||||||
|
if not objs:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if not diff:
|
||||||
|
diff = prototype_diff_from_object(new_prototype, objs[0])
|
||||||
|
|
||||||
|
changed = 0
|
||||||
|
for obj in objs:
|
||||||
|
do_save = False
|
||||||
|
for key, directive in diff.items():
|
||||||
|
val = new_prototype[key]
|
||||||
|
if directive in ('UPDATE', 'REPLACE'):
|
||||||
|
do_save = True
|
||||||
|
if key == 'key':
|
||||||
|
obj.db_key = validate_spawn_value(val, str)
|
||||||
|
elif key == 'typeclass':
|
||||||
|
obj.db_typeclass_path = validate_spawn_value(val, str)
|
||||||
|
elif key == 'location':
|
||||||
|
obj.db_location = validate_spawn_value(val, _to_obj)
|
||||||
|
elif key == 'home':
|
||||||
|
obj.db_home = validate_spawn_value(val, _to_obj)
|
||||||
|
elif key == 'destination':
|
||||||
|
obj.db_destination = validate_spawn_value(val, _to_obj)
|
||||||
|
elif key == 'locks':
|
||||||
|
if directive == 'REPLACE':
|
||||||
|
obj.locks.clear()
|
||||||
|
obj.locks.add(validate_spawn_value(val, str))
|
||||||
|
elif key == 'permissions':
|
||||||
|
if directive == 'REPLACE':
|
||||||
|
obj.permissions.clear()
|
||||||
|
obj.permissions.batch_add(validate_spawn_value(val, make_iter))
|
||||||
|
elif key == 'aliases':
|
||||||
|
if directive == 'REPLACE':
|
||||||
|
obj.aliases.clear()
|
||||||
|
obj.aliases.batch_add(validate_spawn_value(val, make_iter))
|
||||||
|
elif key == 'tags':
|
||||||
|
if directive == 'REPLACE':
|
||||||
|
obj.tags.clear()
|
||||||
|
obj.tags.batch_add(validate_spawn_value(val, make_iter))
|
||||||
|
elif key == 'attrs':
|
||||||
|
if directive == 'REPLACE':
|
||||||
|
obj.attributes.clear()
|
||||||
|
obj.attributes.batch_add(validate_spawn_value(val, make_iter))
|
||||||
|
elif key == 'exec':
|
||||||
|
# we don't auto-rerun exec statements, it would be huge security risk!
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
obj.attributes.add(key, validate_spawn_value(val, _to_obj))
|
||||||
|
elif directive == 'REMOVE':
|
||||||
|
do_save = True
|
||||||
|
if key == 'key':
|
||||||
|
obj.db_key = ''
|
||||||
|
elif key == 'typeclass':
|
||||||
|
# fall back to default
|
||||||
|
obj.db_typeclass_path = settings.BASE_OBJECT_TYPECLASS
|
||||||
|
elif key == 'location':
|
||||||
|
obj.db_location = None
|
||||||
|
elif key == 'home':
|
||||||
|
obj.db_home = None
|
||||||
|
elif key == 'destination':
|
||||||
|
obj.db_destination = None
|
||||||
|
elif key == 'locks':
|
||||||
|
obj.locks.clear()
|
||||||
|
elif key == 'permissions':
|
||||||
|
obj.permissions.clear()
|
||||||
|
elif key == 'aliases':
|
||||||
|
obj.aliases.clear()
|
||||||
|
elif key == 'tags':
|
||||||
|
obj.tags.clear()
|
||||||
|
elif key == 'attrs':
|
||||||
|
obj.attributes.clear()
|
||||||
|
elif key == 'exec':
|
||||||
|
# we don't auto-rerun exec statements, it would be huge security risk!
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
obj.attributes.remove(key)
|
||||||
|
if do_save:
|
||||||
|
changed += 1
|
||||||
|
obj.save()
|
||||||
|
|
||||||
|
return changed
|
||||||
|
|
||||||
|
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): Each paremter tuple will create one object instance using the parameters within.
|
||||||
|
The parameters should be given 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.
|
||||||
|
|
||||||
|
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
|
||||||
|
|
|
||||||
|
|
@ -191,48 +191,6 @@ def validate_spawn_value(value, validator=None):
|
||||||
# Spawner mechanism
|
# Spawner mechanism
|
||||||
|
|
||||||
|
|
||||||
def validate_prototype(prototype, protkey=None, protparents=None, _visited=None):
|
|
||||||
"""
|
|
||||||
Run validation on a prototype, checking for inifinite regress.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
prototype (dict): Prototype to validate.
|
|
||||||
protkey (str, optional): The name of the prototype definition. If not given, the prototype
|
|
||||||
dict needs to have the `prototype_key` field set.
|
|
||||||
protpartents (dict, optional): The available prototype parent library. If
|
|
||||||
note given this will be determined from settings/database.
|
|
||||||
_visited (list, optional): This is an internal work array and should not be set manually.
|
|
||||||
Raises:
|
|
||||||
RuntimeError: If prototype has invalid structure.
|
|
||||||
|
|
||||||
"""
|
|
||||||
if not protparents:
|
|
||||||
protparents = get_protparent_dict()
|
|
||||||
if _visited is None:
|
|
||||||
_visited = []
|
|
||||||
|
|
||||||
protkey = protkey and protkey.lower() or prototype.get('prototype_key', None)
|
|
||||||
|
|
||||||
assert isinstance(prototype, dict)
|
|
||||||
|
|
||||||
if id(prototype) in _visited:
|
|
||||||
raise RuntimeError("%s has infinite nesting of prototypes." % protkey or prototype)
|
|
||||||
|
|
||||||
_visited.append(id(prototype))
|
|
||||||
protstrings = prototype.get("prototype")
|
|
||||||
|
|
||||||
if protstrings:
|
|
||||||
for protstring in make_iter(protstrings):
|
|
||||||
protstring = protstring.lower()
|
|
||||||
if protkey is not None and protstring == protkey:
|
|
||||||
raise RuntimeError("%s tries to prototype itself." % protkey or prototype)
|
|
||||||
protparent = protparents.get(protstring)
|
|
||||||
if not protparent:
|
|
||||||
raise RuntimeError(
|
|
||||||
"%s's prototype '%s' was not found." % (protkey or prototype, protstring))
|
|
||||||
validate_prototype(protparent, protstring, protparents, _visited)
|
|
||||||
|
|
||||||
|
|
||||||
def _get_prototype(dic, prot, protparents):
|
def _get_prototype(dic, prot, protparents):
|
||||||
"""
|
"""
|
||||||
Recursively traverse a prototype dictionary, including multiple
|
Recursively traverse a prototype dictionary, including multiple
|
||||||
|
|
@ -251,202 +209,6 @@ def _get_prototype(dic, prot, protparents):
|
||||||
return prot
|
return prot
|
||||||
|
|
||||||
|
|
||||||
def prototype_diff_from_object(prototype, obj):
|
|
||||||
"""
|
|
||||||
Get a simple diff for a prototype compared to an object which may or may not already have a
|
|
||||||
prototype (or has one but changed locally). For more complex migratations a manual diff may be
|
|
||||||
needed.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
prototype (dict): Prototype.
|
|
||||||
obj (Object): Object to
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
diff (dict): Mapping for every prototype key: {"keyname": "REMOVE|UPDATE|KEEP", ...}
|
|
||||||
|
|
||||||
"""
|
|
||||||
prot1 = prototype
|
|
||||||
prot2 = prototype_from_object(obj)
|
|
||||||
|
|
||||||
diff = {}
|
|
||||||
for key, value in prot1.items():
|
|
||||||
diff[key] = "KEEP"
|
|
||||||
if key in prot2:
|
|
||||||
if callable(prot2[key]) or value != prot2[key]:
|
|
||||||
diff[key] = "UPDATE"
|
|
||||||
elif key not in prot2:
|
|
||||||
diff[key] = "REMOVE"
|
|
||||||
|
|
||||||
return diff
|
|
||||||
|
|
||||||
|
|
||||||
def batch_update_objects_with_prototype(prototype, diff=None, objects=None):
|
|
||||||
"""
|
|
||||||
Update existing objects with the latest version of the prototype.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
prototype (str or dict): Either the `prototype_key` to use or the
|
|
||||||
prototype dict itself.
|
|
||||||
diff (dict, optional): This a diff structure that describes how to update the protototype.
|
|
||||||
If not given this will be constructed from the first object found.
|
|
||||||
objects (list, optional): List of objects to update. If not given, query for these
|
|
||||||
objects using the prototype's `prototype_key`.
|
|
||||||
Returns:
|
|
||||||
changed (int): The number of objects that had changes applied to them.
|
|
||||||
|
|
||||||
"""
|
|
||||||
prototype_key = prototype if isinstance(prototype, basestring) else prototype['prototype_key']
|
|
||||||
prototype_obj = search_db_prototype(prototype_key, return_queryset=True)
|
|
||||||
prototype_obj = prototype_obj[0] if prototype_obj else None
|
|
||||||
new_prototype = prototype_obj.db.prototype
|
|
||||||
objs = ObjectDB.objects.get_by_tag(prototype_key, category=_PROTOTYPE_TAG_CATEGORY)
|
|
||||||
|
|
||||||
if not objs:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
if not diff:
|
|
||||||
diff = prototype_diff_from_object(new_prototype, objs[0])
|
|
||||||
|
|
||||||
changed = 0
|
|
||||||
for obj in objs:
|
|
||||||
do_save = False
|
|
||||||
for key, directive in diff.items():
|
|
||||||
val = new_prototype[key]
|
|
||||||
if directive in ('UPDATE', 'REPLACE'):
|
|
||||||
do_save = True
|
|
||||||
if key == 'key':
|
|
||||||
obj.db_key = validate_spawn_value(val, str)
|
|
||||||
elif key == 'typeclass':
|
|
||||||
obj.db_typeclass_path = validate_spawn_value(val, str)
|
|
||||||
elif key == 'location':
|
|
||||||
obj.db_location = validate_spawn_value(val, _to_obj)
|
|
||||||
elif key == 'home':
|
|
||||||
obj.db_home = validate_spawn_value(val, _to_obj)
|
|
||||||
elif key == 'destination':
|
|
||||||
obj.db_destination = validate_spawn_value(val, _to_obj)
|
|
||||||
elif key == 'locks':
|
|
||||||
if directive == 'REPLACE':
|
|
||||||
obj.locks.clear()
|
|
||||||
obj.locks.add(validate_spawn_value(val, str))
|
|
||||||
elif key == 'permissions':
|
|
||||||
if directive == 'REPLACE':
|
|
||||||
obj.permissions.clear()
|
|
||||||
obj.permissions.batch_add(validate_spawn_value(val, make_iter))
|
|
||||||
elif key == 'aliases':
|
|
||||||
if directive == 'REPLACE':
|
|
||||||
obj.aliases.clear()
|
|
||||||
obj.aliases.batch_add(validate_spawn_value(val, make_iter))
|
|
||||||
elif key == 'tags':
|
|
||||||
if directive == 'REPLACE':
|
|
||||||
obj.tags.clear()
|
|
||||||
obj.tags.batch_add(validate_spawn_value(val, make_iter))
|
|
||||||
elif key == 'attrs':
|
|
||||||
if directive == 'REPLACE':
|
|
||||||
obj.attributes.clear()
|
|
||||||
obj.attributes.batch_add(validate_spawn_value(val, make_iter))
|
|
||||||
elif key == 'exec':
|
|
||||||
# we don't auto-rerun exec statements, it would be huge security risk!
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
obj.attributes.add(key, validate_spawn_value(val, _to_obj))
|
|
||||||
elif directive == 'REMOVE':
|
|
||||||
do_save = True
|
|
||||||
if key == 'key':
|
|
||||||
obj.db_key = ''
|
|
||||||
elif key == 'typeclass':
|
|
||||||
# fall back to default
|
|
||||||
obj.db_typeclass_path = settings.BASE_OBJECT_TYPECLASS
|
|
||||||
elif key == 'location':
|
|
||||||
obj.db_location = None
|
|
||||||
elif key == 'home':
|
|
||||||
obj.db_home = None
|
|
||||||
elif key == 'destination':
|
|
||||||
obj.db_destination = None
|
|
||||||
elif key == 'locks':
|
|
||||||
obj.locks.clear()
|
|
||||||
elif key == 'permissions':
|
|
||||||
obj.permissions.clear()
|
|
||||||
elif key == 'aliases':
|
|
||||||
obj.aliases.clear()
|
|
||||||
elif key == 'tags':
|
|
||||||
obj.tags.clear()
|
|
||||||
elif key == 'attrs':
|
|
||||||
obj.attributes.clear()
|
|
||||||
elif key == 'exec':
|
|
||||||
# we don't auto-rerun exec statements, it would be huge security risk!
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
obj.attributes.remove(key)
|
|
||||||
if do_save:
|
|
||||||
changed += 1
|
|
||||||
obj.save()
|
|
||||||
|
|
||||||
return changed
|
|
||||||
|
|
||||||
|
|
||||||
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): Each paremter tuple will create one object instance using the parameters within.
|
|
||||||
The parameters should be given 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.
|
|
||||||
|
|
||||||
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):
|
def spawn(*prototypes, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
|
@ -472,7 +234,7 @@ def spawn(*prototypes, **kwargs):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# get available protparents
|
# get available protparents
|
||||||
protparents = get_protparent_dict()
|
protparents = {prot['prototype_key']: prot for prot in search_prototype()}
|
||||||
|
|
||||||
# overload module's protparents with specifically given protparents
|
# overload module's protparents with specifically given protparents
|
||||||
protparents.update(kwargs.get("prototype_parents", {}))
|
protparents.update(kwargs.get("prototype_parents", {}))
|
||||||
|
|
|
||||||
|
|
@ -4,91 +4,13 @@ Prototype utilities
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
_PROTOTYPE_META_NAMES = ("prototype_key", "prototype_desc", "prototype_tags", "prototype_locks")
|
||||||
|
|
||||||
|
|
||||||
class PermissionError(RuntimeError):
|
class PermissionError(RuntimeError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def get_protparent_dict():
|
|
||||||
"""
|
|
||||||
Get prototype parents.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
parent_dict (dict): A mapping {prototype_key: prototype} for all available prototypes.
|
|
||||||
|
|
||||||
"""
|
|
||||||
return {prototype['prototype_key']: prototype for prototype in search_prototype()}
|
|
||||||
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
caller (Account or Object): The object requesting the list.
|
|
||||||
key (str, optional): Exact or partial prototype key to query for.
|
|
||||||
tags (str or list, optional): Tag key or keys to query for.
|
|
||||||
show_non_use (bool, optional): Show also prototypes the caller may not use.
|
|
||||||
show_non_edit (bool, optional): Show also prototypes the caller may not edit.
|
|
||||||
Returns:
|
|
||||||
table (EvTable or None): An EvTable representation of the prototypes. None
|
|
||||||
if no prototypes were found.
|
|
||||||
|
|
||||||
"""
|
|
||||||
# this allows us to pass lists of empty strings
|
|
||||||
tags = [tag for tag in make_iter(tags) if tag]
|
|
||||||
|
|
||||||
# get prototypes for readonly and db-based prototypes
|
|
||||||
prototypes = search_prototype(key, tags)
|
|
||||||
|
|
||||||
# get use-permissions of readonly attributes (edit is always False)
|
|
||||||
display_tuples = []
|
|
||||||
for prototype in sorted(prototypes, key=lambda d: d.get('prototype_key', '')):
|
|
||||||
lock_use = caller.locks.check_lockstring(
|
|
||||||
caller, prototype.get('prototype_locks', ''), access_type='use')
|
|
||||||
if not show_non_use and not lock_use:
|
|
||||||
continue
|
|
||||||
if prototype.get('prototype_key', '') in _MODULE_PROTOTYPES:
|
|
||||||
lock_edit = False
|
|
||||||
else:
|
|
||||||
lock_edit = caller.locks.check_lockstring(
|
|
||||||
caller, prototype.get('prototype_locks', ''), access_type='edit')
|
|
||||||
if not show_non_edit and not lock_edit:
|
|
||||||
continue
|
|
||||||
ptags = []
|
|
||||||
for ptag in prototype.get('prototype_tags', []):
|
|
||||||
if is_iter(ptag):
|
|
||||||
if len(ptag) > 1:
|
|
||||||
ptags.append("{} (category: {}".format(ptag[0], ptag[1]))
|
|
||||||
else:
|
|
||||||
ptags.append(ptag[0])
|
|
||||||
else:
|
|
||||||
ptags.append(str(ptag))
|
|
||||||
|
|
||||||
display_tuples.append(
|
|
||||||
(prototype.get('prototype_key', '<unset>'),
|
|
||||||
prototype.get('prototype_desc', '<unset>'),
|
|
||||||
"{}/{}".format('Y' if lock_use else 'N', 'Y' if lock_edit else 'N'),
|
|
||||||
",".join(ptags)))
|
|
||||||
|
|
||||||
if not display_tuples:
|
|
||||||
return None
|
|
||||||
|
|
||||||
table = []
|
|
||||||
width = 78
|
|
||||||
for i in range(len(display_tuples[0])):
|
|
||||||
table.append([str(display_tuple[i]) for display_tuple in display_tuples])
|
|
||||||
table = EvTable("Key", "Desc", "Use/Edit", "Tags", table=table, crop=True, width=width)
|
|
||||||
table.reformat_column(0, width=22)
|
|
||||||
table.reformat_column(1, width=29)
|
|
||||||
table.reformat_column(2, width=11, align='c')
|
|
||||||
table.reformat_column(3, width=16)
|
|
||||||
return table
|
|
||||||
|
|
||||||
|
|
||||||
def prototype_to_str(prototype):
|
def prototype_to_str(prototype):
|
||||||
"""
|
"""
|
||||||
Format a prototype to a nice string representation.
|
Format a prototype to a nice string representation.
|
||||||
|
|
@ -111,40 +33,30 @@ def prototype_to_str(prototype):
|
||||||
return header + proto
|
return header + proto
|
||||||
|
|
||||||
|
|
||||||
def prototype_from_object(obj):
|
def prototype_diff_from_object(prototype, obj):
|
||||||
"""
|
"""
|
||||||
Guess a minimal prototype from an existing object.
|
Get a simple diff for a prototype compared to an object which may or may not already have a
|
||||||
|
prototype (or has one but changed locally). For more complex migratations a manual diff may be
|
||||||
|
needed.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
obj (Object): An object to analyze.
|
prototype (dict): Prototype.
|
||||||
|
obj (Object): Object to
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
prototype (dict): A prototype estimating the current state of the object.
|
diff (dict): Mapping for every prototype key: {"keyname": "REMOVE|UPDATE|KEEP", ...}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# first, check if this object already has a prototype
|
prot1 = prototype
|
||||||
|
prot2 = prototype_from_object(obj)
|
||||||
|
|
||||||
prot = obj.tags.get(category=_PROTOTYPE_TAG_CATEGORY, return_list=True)
|
diff = {}
|
||||||
prot = search_prototype(prot)
|
for key, value in prot1.items():
|
||||||
if not prot or len(prot) > 1:
|
diff[key] = "KEEP"
|
||||||
# no unambiguous prototype found - build new prototype
|
if key in prot2:
|
||||||
prot = {}
|
if callable(prot2[key]) or value != prot2[key]:
|
||||||
prot['prototype_key'] = "From-Object-{}-{}".format(
|
diff[key] = "UPDATE"
|
||||||
obj.key, hashlib.md5(str(time.time())).hexdigest()[:6])
|
elif key not in prot2:
|
||||||
prot['prototype_desc'] = "Built from {}".format(str(obj))
|
diff[key] = "REMOVE"
|
||||||
prot['prototype_locks'] = "use:all();edit:all()"
|
|
||||||
|
|
||||||
prot['key'] = obj.db_key or hashlib.md5(str(time.time())).hexdigest()[:6]
|
return diff
|
||||||
prot['location'] = obj.db_location
|
|
||||||
prot['home'] = obj.db_home
|
|
||||||
prot['destination'] = obj.db_destination
|
|
||||||
prot['typeclass'] = obj.db_typeclass_path
|
|
||||||
prot['locks'] = obj.locks.all()
|
|
||||||
prot['permissions'] = obj.permissions.get()
|
|
||||||
prot['aliases'] = obj.aliases.get()
|
|
||||||
prot['tags'] = [(tag.key, tag.category, tag.data)
|
|
||||||
for tag in obj.tags.get(return_tagobj=True, return_list=True)]
|
|
||||||
prot['attrs'] = [(attr.key, attr.value, attr.category, attr.locks)
|
|
||||||
for attr in obj.attributes.get(return_obj=True, return_list=True)]
|
|
||||||
|
|
||||||
return prot
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue