From a29b46d0915cc95d672ce482010840420bd759b4 Mon Sep 17 00:00:00 2001 From: Griatch Date: Wed, 19 Sep 2018 22:51:27 +0200 Subject: [PATCH] Cleanup, bug fixes, refactoring --- evennia/prototypes/menus.py | 2 +- evennia/prototypes/prototypes.py | 514 ++++++++++++++++-------------- evennia/prototypes/spawner.py | 53 +-- evennia/prototypes/tests.py | 135 +++++--- evennia/typeclasses/attributes.py | 2 +- 5 files changed, 404 insertions(+), 302 deletions(-) diff --git a/evennia/prototypes/menus.py b/evennia/prototypes/menus.py index 1c7ae1ea92..e18e467e35 100644 --- a/evennia/prototypes/menus.py +++ b/evennia/prototypes/menus.py @@ -1092,7 +1092,7 @@ def _add_attr(caller, attr_string, **kwargs): attrname, category = nameparts elif nparts > 2: attrname, category, locks = nameparts - attr_tuple = (attrname, value, category, locks) + attr_tuple = (attrname, value, category, str(locks)) if attrname: prot = _get_menu_prototype(caller) diff --git a/evennia/prototypes/prototypes.py b/evennia/prototypes/prototypes.py index 0cc016300f..0843a67105 100644 --- a/evennia/prototypes/prototypes.py +++ b/evennia/prototypes/prototypes.py @@ -1,7 +1,7 @@ """ Handling storage of prototypes, both database-based ones (DBPrototypes) and those defined in modules -(Read-only prototypes). +(Read-only prototypes). Also contains utility functions, formatters and manager functions. """ @@ -31,7 +31,6 @@ _PROTOTYPE_TAG_CATEGORY = "from_prototype" _PROTOTYPE_TAG_META_CATEGORY = "db_prototype" PROT_FUNCS = {} - _RE_DBREF = re.compile(r"(? (any): value of a nattribute (ndb_ is stripped) + ndb_ (any): value of a nattribute (ndb_ is stripped) - this is of limited use. other (any): any other name is interpreted as the key of an Attribute with its value. Such Attributes have no categories. @@ -66,15 +72,16 @@ return the value to enter into the field and will be called every time the prototype is used to spawn an object. Note, if you want to store a callable in an Attribute, embed it in a tuple to the `args` keyword. -By specifying the "prototype" key, the prototype becomes a child of -that prototype, inheritng all prototype slots it does not explicitly +By specifying the "prototype_parent" key, the prototype becomes a child of +the given prototype, inheritng all prototype slots it does not explicitly define itself, while overloading those that it does specify. ```python import random -GOBLIN_WIZARD = { +{ + "prototype_key": "goblin_wizard", "prototype_parent": GOBLIN, "key": "goblin wizard", "spells": ["fire ball", "lighting bolt"] @@ -189,7 +196,9 @@ def flatten_prototype(prototype, validate=False): flattened (dict): The final, flattened prototype. """ + if prototype: + prototype = protlib.homogenize_prototype(prototype) protparents = {prot['prototype_key'].lower(): prot for prot in protlib.search_prototype()} protlib.validate_prototype(prototype, None, protparents, is_prototype_base=validate, strict=validate) @@ -253,7 +262,7 @@ def prototype_from_object(obj): for tag in obj.tags.get(return_tagobj=True, return_list=True) if tag] if tags: prot['tags'] = tags - attrs = [(attr.key, attr.value, attr.category, attr.locks.all()) + attrs = [(attr.key, attr.value, attr.category, ';'.join(attr.locks.all())) for attr in obj.attributes.get(return_obj=True, return_list=True) if attr] if attrs: prot['attrs'] = attrs @@ -261,7 +270,7 @@ def prototype_from_object(obj): return prot -def prototype_diff(prototype1, prototype2): +def prototype_diff(prototype1, prototype2, maxdepth=2): """ A 'detailed' diff specifies differences down to individual sub-sectiions of the prototype, like individual attributes, permissions etc. It is used @@ -270,6 +279,9 @@ def prototype_diff(prototype1, prototype2): Args: prototype1 (dict): Original prototype. prototype2 (dict): Comparison prototype. + maxdepth (int, optional): The maximum depth into the diff we go before treating the elements + of iterables as individual entities to compare. This is important since a single + attr/tag (for example) are represented by a tuple. Returns: diff (dict): A structure detailing how to convert prototype1 to prototype2. All @@ -280,7 +292,7 @@ def prototype_diff(prototype1, prototype2): instruction can be one of "REMOVE", "ADD", "UPDATE" or "KEEP". """ - def _recursive_diff(old, new): + def _recursive_diff(old, new, depth=0): old_type = type(old) new_type = type(new) @@ -292,14 +304,14 @@ def prototype_diff(prototype1, prototype2): return (old, new, "ADD") else: return (old, new, "UPDATE") - elif new_type == dict: + elif depth < maxdepth and new_type == dict: all_keys = set(old.keys() + new.keys()) - return {key: _recursive_diff(old.get(key), new.get(key)) for key in all_keys} - elif is_iter(new): + return {key: _recursive_diff(old.get(key), new.get(key), depth=depth + 1) for key in all_keys} + elif depth < maxdepth and is_iter(new): old_map = {part[0] if is_iter(part) else part: part for part in old} new_map = {part[0] if is_iter(part) else part: part for part in new} all_keys = set(old_map.keys() + new_map.keys()) - return {key: _recursive_diff(old_map.get(key), new_map.get(key)) for key in all_keys} + return {key: _recursive_diff(old_map.get(key), new_map.get(key), depth=depth + 1) for key in all_keys} elif old != new: return (old, new, "UPDATE") else: @@ -346,13 +358,13 @@ def flatten_diff(diff): typ = type(diffpart) if typ == tuple and len(diffpart) == 3 and diffpart[2] in valid_instructions: out = [diffpart[2]] - elif type == dict: + elif typ == dict: # all other are dicts for val in diffpart.values(): out.extend(_get_all_nested_diff_instructions(val)) else: raise RuntimeError("Diff contains non-dicts that are not on the " - "form (old, new, inst): {}".format(diff)) + "form (old, new, inst): {}".format(diffpart)) return out flat_diff = {} @@ -402,7 +414,7 @@ def prototype_diff_from_object(prototype, obj): """ obj_prototype = prototype_from_object(obj) - diff = prototype_diff(obj_prototype, prototype) + diff = prototype_diff(obj_prototype, protlib.homogenize_prototype(prototype)) return diff, obj_prototype @@ -421,6 +433,8 @@ def batch_update_objects_with_prototype(prototype, diff=None, objects=None): changed (int): The number of objects that had changes applied to them. """ + prototype = protlib.homogenize_prototype(prototype) + if isinstance(prototype, basestring): new_prototype = protlib.search_prototype(prototype) else: @@ -439,7 +453,6 @@ def batch_update_objects_with_prototype(prototype, diff=None, objects=None): # make sure the diff is flattened diff = flatten_diff(diff) - changed = 0 for obj in objects: do_save = False @@ -619,9 +632,9 @@ def spawn(*prototypes, **kwargs): (no object creation) and return the create-kwargs. Returns: - object (Object, dict or list): Spawned object. If `only_validate` is given, return + object (Object, dict or list): Spawned object(s). If `only_validate` is given, return a list of the creation kwargs to build the object(s) without actually creating it. If - `return_parents` is set, return dict of prototype parents. + `return_parents` is set, instead return dict of prototype parents. """ # get available protparents diff --git a/evennia/prototypes/tests.py b/evennia/prototypes/tests.py index 1c77fd85c3..9f782f991b 100644 --- a/evennia/prototypes/tests.py +++ b/evennia/prototypes/tests.py @@ -70,7 +70,7 @@ class TestUtils(EvenniaTest): self.obj1.tags.add('foo') new_prot = spawner.prototype_from_object(self.obj1) self.assertEqual( - {'attrs': [('test', 'testval', None, [''])], + {'attrs': [('test', 'testval', None, '')], 'home': Something, 'key': 'Obj', 'location': Something, @@ -94,14 +94,15 @@ class TestUtils(EvenniaTest): def test_update_objects_from_prototypes(self): self.maxDiff = None - self.obj1.attributes.add('oldtest', 'to_remove') + self.obj1.attributes.add('oldtest', 'to_keep') old_prot = spawner.prototype_from_object(self.obj1) # modify object away from prototype self.obj1.attributes.add('test', 'testval') + self.obj1.attributes.add('desc', 'changed desc') self.obj1.aliases.add('foo') - self.obj1.key = 'NewObj' + self.obj1.tags.add('footag', 'foocategory') # modify prototype old_prot['new'] = 'new_val' @@ -109,53 +110,111 @@ class TestUtils(EvenniaTest): old_prot['permissions'] = 'Builder' # this will not update, since we don't update the prototype on-disk old_prot['prototype_desc'] = 'New version of prototype' + old_prot['attrs'] += (("fooattr", "fooattrval", None, ''),) # diff obj/prototype - pdiff = spawner.prototype_diff_from_object(old_prot, self.obj1) + old_prot_copy = old_prot.copy() + pdiff, obj_prototype = spawner.prototype_diff_from_object(old_prot, self.obj1) + + self.assertEqual(old_prot_copy, old_prot) + + self.assertEqual(obj_prototype, + {'aliases': ['foo'], + 'attrs': [('oldtest', 'to_keep', None, ''), + ('test', 'testval', None, ''), + ('desc', 'changed desc', None, '')], + 'key': 'Obj', + 'home': '#1', + 'location': '#1', + 'locks': 'call:true();control:perm(Developer);delete:perm(Admin);' + 'edit:perm(Admin);examine:perm(Builder);get:all();' + 'puppet:pperm(Developer);tell:perm(Admin);view:all()', + 'prototype_desc': 'Built from Obj', + 'prototype_key': Something, + 'prototype_locks': 'spawn:all();edit:all()', + 'prototype_tags': [], + 'typeclass': 'evennia.objects.objects.DefaultObject'}) + + self.assertEqual(old_prot, + {'attrs': [('oldtest', 'to_keep', None, ''), + ('fooattr', 'fooattrval', None, '')], + 'home': '#1', + 'key': 'Obj', + 'location': '#1', + 'locks': 'call:true();control:perm(Developer);delete:perm(Admin);' + 'edit:perm(Admin);examine:perm(Builder);get:all();' + 'puppet:pperm(Developer);tell:perm(Admin);view:all()', + 'new': 'new_val', + 'permissions': 'Builder', + 'prototype_desc': 'New version of prototype', + 'prototype_key': Something, + 'prototype_locks': 'spawn:all();edit:all()', + 'prototype_tags': [], + 'test': 'testval_changed', + 'typeclass': 'evennia.objects.objects.DefaultObject'}) + + # from evennia import set_trace; set_trace(term_size=(182, 50)) self.assertEqual( pdiff, - ({'aliases': 'REMOVE', - 'attrs': 'REPLACE', - 'home': 'KEEP', - 'key': 'UPDATE', - 'location': 'KEEP', - 'locks': 'KEEP', - 'new': 'UPDATE', - 'permissions': 'UPDATE', - 'prototype_desc': 'UPDATE', - 'prototype_key': 'UPDATE', - 'prototype_locks': 'KEEP', - 'prototype_tags': 'KEEP', - 'test': 'UPDATE', - 'typeclass': 'KEEP'}, - {'attrs': [('oldtest', 'to_remove', None, ['']), - ('test', 'testval', None, [''])], - 'prototype_locks': 'spawn:all();edit:all()', - 'prototype_key': Something, - 'locks': ";".join([ - 'call:true()', 'control:perm(Developer)', - 'delete:perm(Admin)', 'edit:perm(Admin)', - 'examine:perm(Builder)', 'get:all()', - 'puppet:pperm(Developer)', 'tell:perm(Admin)', - 'view:all()']), - 'prototype_tags': [], - 'location': "#1", - 'key': 'NewObj', - 'home': '#1', - 'typeclass': 'evennia.objects.objects.DefaultObject', - 'prototype_desc': 'Built from NewObj', - 'aliases': 'foo'}) + {'home': ('#1', '#1', 'KEEP'), + 'prototype_locks': ('spawn:all();edit:all()', + 'spawn:all();edit:all()', 'KEEP'), + 'prototype_key': (Something, Something, 'UPDATE'), + 'location': ('#1', '#1', 'KEEP'), + 'locks': ('call:true();control:perm(Developer);delete:perm(Admin);' + 'edit:perm(Admin);examine:perm(Builder);get:all();' + 'puppet:pperm(Developer);tell:perm(Admin);view:all()', + 'call:true();control:perm(Developer);delete:perm(Admin);' + 'edit:perm(Admin);examine:perm(Builder);get:all();' + 'puppet:pperm(Developer);tell:perm(Admin);view:all()', 'KEEP'), + 'prototype_tags': {}, + 'attrs': {'oldtest': (('oldtest', 'to_keep', None, ''), + ('oldtest', 'to_keep', None, ''), 'KEEP'), + 'test': (('test', 'testval', None, ''), + None, 'REMOVE'), + 'desc': (('desc', 'changed desc', None, ''), + None, 'REMOVE'), + 'fooattr': (None, ('fooattr', 'fooattrval', None, ''), 'ADD'), + 'test': (('test', 'testval', None, ''), + ('test', 'testval_changed', None, ''), 'UPDATE'), + 'new': (None, ('new', 'new_val', None, ''), 'ADD')}, + 'key': ('Obj', 'Obj', 'KEEP'), + 'typeclass': ('evennia.objects.objects.DefaultObject', + 'evennia.objects.objects.DefaultObject', 'KEEP'), + 'aliases': (['foo'], None, 'REMOVE'), + 'prototype_desc': ('Built from Obj', + 'New version of prototype', 'UPDATE'), + 'permissions': (None, 'Builder', 'ADD')} ) + # from evennia import set_trace;set_trace() + self.assertEqual( + spawner.flatten_diff(pdiff), + {'aliases': 'REMOVE', + 'attrs': 'REPLACE', + 'home': 'KEEP', + 'key': 'KEEP', + 'location': 'KEEP', + 'locks': 'KEEP', + 'permissions': 'UPDATE', + 'prototype_desc': 'UPDATE', + 'prototype_key': 'UPDATE', + 'prototype_locks': 'KEEP', + 'prototype_tags': 'KEEP', + 'typeclass': 'KEEP'} + ) + # apply diff count = spawner.batch_update_objects_with_prototype( - old_prot, diff=pdiff[0], objects=[self.obj1]) + old_prot, diff=pdiff, objects=[self.obj1]) self.assertEqual(count, 1) new_prot = spawner.prototype_from_object(self.obj1) - self.assertEqual({'attrs': [('test', 'testval_changed', None, ['']), - ('new', 'new_val', None, [''])], + self.assertEqual({'attrs': [('oldtest', 'to_keep', None, ''), + ('fooattr', 'fooattrval', None, ''), + ('new', 'new_val', None, ''), + ('test', 'testval_changed', None, '')], 'home': Something, 'key': 'Obj', 'location': Something, diff --git a/evennia/typeclasses/attributes.py b/evennia/typeclasses/attributes.py index eb698e6f0e..7c7280c448 100644 --- a/evennia/typeclasses/attributes.py +++ b/evennia/typeclasses/attributes.py @@ -564,7 +564,7 @@ class AttributeHandler(object): ntup = len(tup) keystr = str(tup[0]).strip().lower() new_value = tup[1] - category = str(tup[2]).strip().lower() if ntup > 2 else None + category = str(tup[2]).strip().lower() if ntup > 2 and tup[2] is not None else None lockstring = tup[3] if ntup > 3 else "" attr_objs = self._getcache(keystr, category)