From d13878a7ce41238a2dce80414ead8d9a517cbaf1 Mon Sep 17 00:00:00 2001 From: evilchili Date: Sun, 31 Dec 2023 10:15:15 -0800 Subject: [PATCH] fix bug where overrides are applied after templates are processed --- dnd_item/types.py | 22 ++++++++++++---------- tests/test_types.py | 40 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 51 insertions(+), 11 deletions(-) diff --git a/dnd_item/types.py b/dnd_item/types.py index fff3828..da5f0f9 100644 --- a/dnd_item/types.py +++ b/dnd_item/types.py @@ -156,7 +156,7 @@ class Item(AttributeMap): ? length=10, ? properties=dict( ? 'broken'=dict( - ? description="The end of this {length}ft. pole has been snapped off.", + ? description="The end of this 10ft. pole has been snapped off.", ? override_length=7 ? ), ? ) @@ -199,10 +199,6 @@ class Item(AttributeMap): converted to Item objects; everything else is passed as-is. """ - # delay processing the 'properties' attribute until after the other - # attributes, because they may contain references to those attributes. - properties = attrs.pop("properties", None) - attributes = dict() # recursively locate and populate template strings @@ -233,19 +229,25 @@ class Item(AttributeMap): # Any type other than dict, list, and string is returned unaltered. return obj + properties = attrs.pop("properties", {}) + + # apply property overrides overrides before anything else + for prop in properties.values(): + overrides = [k for k in prop.keys() if k.startswith("override_")] + for o in overrides: + if prop[o]: + attrs[o.replace("override_", "")] = prop[o] + # step through the supplied attributes and format each member. for k, v in sorted(attrs.items(), key=lambda i: '{' in f"{i[0]}{i[1]}"): if type(v) is dict: attributes[k] = AttributeMap.from_dict(_format(v)) else: attributes[k] = _format(v) + + # process properties now that we have preprocessed everything else if properties: attributes["properties"] = AttributeMap.from_dict(_format(properties)) - for prop in attributes["properties"].values(): - overrides = [k for k in prop.attributes.keys() if k.startswith("override_")] - for o in overrides: - if prop.attributes[o]: - attributes[o.replace("override_", "")] = prop.attributes[o] # store the item name as the _name attribute; it is accessable directly, or # via the name property. This makes overriding the name convenient for subclassers, diff --git a/tests/test_types.py b/tests/test_types.py index 4466a77..7bb9cec 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -1,6 +1,28 @@ from dnd_item import types +def test_AttributeMap(): + assert types.AttributeMap(attributes={'foo': True, 'bar': False}).foo is True + + +def test_AttributeMap_nested(): + nested_dict = {'foo': {'bar': {'baz': True}, 'boz': False}} + amap = types.AttributeMap.from_dict(nested_dict) + assert amap.foo.bar.baz is True + assert amap.foo.boz is False + + +def test_AttributeMap_list(): + amap = types.AttributeMap(attributes={'foo': True, 'bar': False}) + assert list(amap.attributes.keys()) == ['foo', 'bar'] + + +def test_AttributeMap_mapping(): + amap = types.AttributeMap(attributes={'foo': True, 'bar': False}) + assert 'foo' in amap + assert '{foo}, {bar}'.format(**amap) == 'True, False' + + def test_Item_attributes(): assert types.Item.from_dict(dict( name='{length}ft. Pole', @@ -10,7 +32,7 @@ def test_Item_attributes(): )).name == '10ft. Pole' -def test_item_nested_attributes(): +def test_Item_nested_attributes(): assert types.Item.from_dict(dict( name='{length}ft. Pole', length=10, @@ -23,3 +45,19 @@ def test_item_nested_attributes(): owner='Jules Ultardottir', ) )).description == 'Engraved. "Property of Jules Ultardottir!"' + + +def test_Item_overrides(): + attrs = dict( + name='{length}ft. Pole', + length=10, + properties=dict( + broken=dict( + description="The end of this 10ft. pole has been snapped off.", + override_length=7 + ), + ) + ) + ten_foot_pole = types.Item.from_dict(attrs) + assert ten_foot_pole.name == '7ft. Pole' + assert ten_foot_pole.description == 'Broken. The end of this 10ft. pole has been snapped off.'