diff --git a/CHANGES b/CHANGES index 2bf004f8..7e2910ad 100644 --- a/CHANGES +++ b/CHANGES @@ -13,6 +13,8 @@ Changes 0.38.0 (*unreleased*) ====================== +- fix #362: identify the task when ``config_changed`` cannot JSON-serialize + its configuration, preserving the original error as the exception cause. - site/docs: landing page is now generated by sphinx (``doc/index.rst`` using ``sphinx-design``), replacing the hand-written ``doc/index.html`` and its vendored bootstrap / font-awesome / owl-carousel assets. diff --git a/doit/tools.py b/doit/tools.py index ac4214f8..16215ad6 100644 --- a/doit/tools.py +++ b/doit/tools.py @@ -73,7 +73,12 @@ def configure_task(self, task): def __call__(self, task, values): """return True if config values are UNCHANGED""" - self.config_digest = self._calc_digest() + try: + self.config_digest = self._calc_digest() + except TypeError as error: + msg = (f"Task '{task.name}': config_changed could not serialize " + f"configuration: {error}") + raise TypeError(msg) from error last_success = values.get('_config_changed') if last_success is None: return False diff --git a/tests/test_tools.py b/tests/test_tools.py index 6cbd5970..0eecd66e 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -1,6 +1,7 @@ import os import datetime import json +from pathlib import Path import operator import unittest from sys import executable @@ -55,6 +56,20 @@ class NotValid(object): pass uptodate = tools.config_changed(NotValid()) self.assertRaises(Exception, uptodate, None, None) + def test_unserializable_config_identifies_task(self): + for config in ({'source': Path('input')}, + {'nested': {'source': Path('input')}}, + {Path('input'): 'value'}): + with self.subTest(config=config): + check = tools.config_changed(config) + current_task = task.Task('build:assets', None, uptodate=[check]) + with self.assertRaisesRegex( + TypeError, "build:assets.*config_changed") as raised: + check(current_task, {}) + self.assertIsInstance(raised.exception.__cause__, TypeError) + self.assertIn('Path', str(raised.exception.__cause__)) + self.assertIsNone(check.config_digest) + def test_string(self): ua = tools.config_changed('a') ub = tools.config_changed('b')