theLaborInVain / kdm-manager-api

The API used by https://kdm-manager.com and related Kingdom Death: Monster utilities.
Other
3 stars 0 forks source link

Survivor Sheet: updates fail due to Weapon Mastery auto-application failure #55

Closed toconnell closed 2 years ago

toconnell commented 2 years ago

The first one came through like this:

User OID: 5c6bb3c08740d944f8dce670 Method: POST URL: http://api.kdm-manager.com/survivor/set_attribute/613dfb655c3654f1e4d5997c JSON: {'attribute': 'Insanity', 'value': 3, 'serialize_on_response': True}

Traceback (most recent call last):
File "/home/toconnell/kdm-manager-api/app/models/__init__.py", line 118, in wrapper
 return log_event_call(self, *args, **kwargs)
File "/home/toconnell/kdm-manager-api/app/models/__init__.py", line 1480, in log_event
 "settlement_id": self.settlement_id,
AttributeError: 'Survivor' object has no attribute 'settlement_id'

But this settlement has dozens of these:

User OID: 5c6bb3c08740d944f8dce670 Method: POST URL: http://api.kdm-manager.com/survivor/set_attribute/613dfb655c3654f1e4d5997c JSON: {'attribute': 'Insanity', 'value': 3, 'serialize_on_response': True}


Traceback (most recent call last):
File "/home/toconnell/kdm-manager-api/venv/lib/python3.6/site-packages/flask/app.py", line 1950, in full_dispatch_request
 rv = self.dispatch_request()
File "/home/toconnell/kdm-manager-api/venv/lib/python3.6/site-packages/flask/app.py", line 1936, in dispatch_request
 return self.view_functions[rule.endpoint](**req.view_args)
File "/home/toconnell/kdm-manager-api/app/utils/crossdomain.py", line 56, in wrapped_function
 resp = flask.make_response(func(*args, **kwargs))
File "/home/toconnell/kdm-manager-api/app/routes.py", line 469, in collection_action
 asset_object = assets.get_user_asset(collection, asset_id)
File "/home/toconnell/kdm-manager-api/app/assets/__init__.py", line 89, in get_user_asset
 return survivors.Survivor(_id=asset_id)
File "/home/toconnell/kdm-manager-api/app/models/survivors/__init__.py", line 201, in __init__
 normalize_on_init=False
File "/home/toconnell/kdm-manager-api/app/utils/__init__.py", line 285, in not_timed
 return method(*args, **kwargs)
File "/home/toconnell/kdm-manager-api/app/models/settlements.py", line 130, in __init__
 models.UserAsset.__init__(self, *args, **kwargs)
File "/home/toconnell/kdm-manager-api/app/models/__init__.py", line 1010, in __init__
 self.load()
File "/home/toconnell/kdm-manager-api/app/models/settlements.py", line 152, in load
 self.get_survivors('initialize') # sets a list of objects
File "/home/toconnell/kdm-manager-api/app/models/settlements.py", line 3491, in get_survivors
 S = survivors.Survivor(_id=s["_id"], Settlement=self, normalize_on_init=False)
File "/home/toconnell/kdm-manager-api/app/models/survivors/__init__.py", line 191, in __init__
 models.UserAsset.__init__(self, *args, **kwargs)
File "/home/toconnell/kdm-manager-api/app/models/__init__.py", line 1010, in __init__
 self.load()
File "/home/toconnell/kdm-manager-api/app/models/survivors/__init__.py", line 212, in load
 super().load()
File "/home/toconnell/kdm-manager-api/app/models/__init__.py", line 1045, in load
 self.set_last_accessed(save=True)
File "/home/toconnell/kdm-manager-api/app/models/__init__.py", line 1344, in set_last_accessed
 self.save(False)
File "/home/toconnell/kdm-manager-api/app/models/survivors/__init__.py", line 230, in save
 self.post_process()
File "/home/toconnell/kdm-manager-api/app/models/survivors/__init__.py", line 254, in post_process
 self.add_game_asset("abilities_and_impairments", w_dict['master_ai'], save=False)
File "/home/toconnell/kdm-manager-api/app/models/survivors/__init__.py", line 1030, in add_game_asset
 self.Settlement.add_innovation(asset_dict["handle"])
File "/home/toconnell/kdm-manager-api/app/models/settlements.py", line 1085, in add_innovation
 i_dict = self.Innovations.get_asset(i_handle)
AttributeError: 'Settlement' object has no attribute 'Innovations'
toconnell commented 2 years ago

It looks like this is actually bombing out during settlement load:

[2021-09-15 08:56:43] DEBUG:    PERSEO [M] (613e02cd2469bec8182c676a) saving during 'set_attribute' action! Starting post-process...
[2021-09-15 08:56:43] WARNING:  PERSEO [M] (613e02cd2469bec8182c676a) Weapon mastery reached, but A&I not present! Adding A&I...
[2021-09-15 08:56:43] ERROR:    Unhandled exception in log_event() method!args: (), kwargs: {'action': 'add', 'key': 'epithets', 'value': 'Bow Master'}
Traceback (most recent call last):
  File "/home/toconnell/kdm-manager-api/app/models/__init__.py", line 122, in wrapper
    return log_event_call(self, *args, **kwargs)
  File "/home/toconnell/kdm-manager-api/app/models/__init__.py", line 1484, in log_event
    "settlement_id": self.settlement_id,
AttributeError: 'Survivor' object has no attribute 'settlement_id'
toconnell commented 2 years ago

Alright, so first up, let's mitigate the bad code in semantic logging:

diff --git a/app/models/__init__.py b/app/models/__init__.py
index c3d411e..378cd90 100644
--- a/app/models/__init__.py
+++ b/app/models/__init__.py
@@ -1481,7 +1481,8 @@ class UserAsset(object):
             "created_on": datetime.now(),
             'created_by': created_by,
             'created_by_email': created_by_email,
-            "settlement_id": self.settlement_id,
+#            "settlement_id": self.settlement_id,
+            "settlement_id": getattr(self, 'settlement_id', None),
             "ly": self.get_current_ly(),
             'event_type': event_type,
             'event': msg,
toconnell commented 2 years ago

This next thing we're hitting:

[2021-09-15 09:08:38] ERROR:    Could not load _id '613e02a55c3654f1e4d59a60' from settlements!
[2021-09-15 09:08:38] ERROR:    'Settlement' object has no attribute 'Innovations'

This only happens when a settlement doesn't have a chance to call init_asset_collections() (or, I guess, if that fails).

So what's happening here, is a survivor has a weapon mastery (based on points) and we're trying to add it to the settlement's innovations and exploding:

  File "/home/toconnell/kdm-manager-api/app/models/survivors/__init__.py", line 254, in post_process
    self.add_game_asset("abilities_and_impairments", w_dict['master_ai'], save=False)
  File "/home/toconnell/kdm-manager-api/app/models/survivors/__init__.py", line 1030, in add_game_asset
    self.Settlement.add_innovation(asset_dict["handle"])
  File "/home/toconnell/kdm-manager-api/app/models/settlements.py", line 1085, in add_innovation
    i_dict = self.Innovations.get_asset(i_handle)

...which looks like the survivor's (not the settlement's) self.Settlement isn't being initialized correctly?

toconnell commented 2 years ago

Yeah, OK: looks like survivors are calling the base class load() on init, rather than their private version.

toconnell commented 2 years ago

I think I cracked the case.

For some idiotic reason, the Survivor object was initializing by calling its base class init() method directly:

-        models.UserAsset.__init__(self,  *args, **kwargs)

Which should be using super(), since we're using new-style classes now:

+        super().__init__(self,  *args, **kwargs)
toconnell commented 2 years ago

Alright, I figured it out. The problem is that the _set_lastaccessed() method fires whenever a survivor object is initialized; problem is, the private (not the base class, but the child class) save() method for the Survivor object kicks off the post-process code, which expects an initialized object, not one that is in the process of being initialized.

toconnell commented 2 years ago
-        if hasattr(request, 'action') and request.action in post_process_routes:
+
+        # never post-process an uninitialized Survivor:
+        if getattr(self, 'Settlement', None) is None:
+            bypass_post_process = True
+
+        if (
+            not bypass_post_process and
+            hasattr(request, 'action') and
+            request.action in post_process_routes
+        ):
             self.post_process()