mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-25 09:32:37 +00:00
core tests - very readable error for unpredicted return values
Previously:
E TypeError: Object of type Prophecy is not JSON serializable
Now:
E TypeError: Failed to serialize call: '{'name': 'duplicate_ifc_objects', 'args': ([<Prophecy for 'bonsai.core.tool.Ifc'>],), 'kwargs': {}}'.
E Possibly due to unpredicted return value for some call.
E See the list of the recent calls without return values:
E - {'name': 'get_object', 'args': ('annotation',), 'kwargs': {}}
E - {'name': 'run', 'args': ('group.assign_group',), 'kwargs': {'group': 'new_group', 'products': ['new_drawing']}}
E - {'name': 'run', 'args': ('group.edit_group',), 'kwargs': {'group': 'new_group', 'attributes': {'Name': 'unique_name', 'ObjectType': 'DRAWING'}}}
E - {'name': 'run', 'args': ('group.unassign_group',), 'kwargs': {'group': 'group', 'products': ['new_drawing']}}
This commit is contained in:
@@ -241,6 +241,14 @@ def voider():
|
|||||||
prophet.verify()
|
prophet.verify()
|
||||||
|
|
||||||
|
|
||||||
|
def flatten(iterable):
|
||||||
|
for item in iterable:
|
||||||
|
if isinstance(item, (list, tuple)):
|
||||||
|
yield from flatten(item)
|
||||||
|
else:
|
||||||
|
yield item
|
||||||
|
|
||||||
|
|
||||||
Call = TypedDict("Call", {"name": str, "args": tuple[Any, ...], "kwargs": dict[str, Any]})
|
Call = TypedDict("Call", {"name": str, "args": tuple[Any, ...], "kwargs": dict[str, Any]})
|
||||||
Prediction = TypedDict("Prediction", {"type": Literal["SHOULD_BE_CALLED"], "number": Optional[int], "call": Call})
|
Prediction = TypedDict("Prediction", {"type": Literal["SHOULD_BE_CALLED"], "number": Optional[int], "call": Call})
|
||||||
|
|
||||||
@@ -270,6 +278,13 @@ class Prophecy:
|
|||||||
self.return_values: dict[str, Any] = {}
|
self.return_values: dict[str, Any] = {}
|
||||||
self.should_call: Optional[Call] = None
|
self.should_call: Optional[Call] = None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def serialize(call: Call) -> str:
|
||||||
|
return json.dumps(call, sort_keys=True)
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"<Prophecy for '{self.subject.__original_qualname__}'>"
|
||||||
|
|
||||||
def __getattr__(self, attr: str):
|
def __getattr__(self, attr: str):
|
||||||
if not hasattr(self.subject, attr):
|
if not hasattr(self.subject, attr):
|
||||||
raise AttributeError(f"Interface '{self.subject.__original_qualname__}' has no attribute '{attr}'.")
|
raise AttributeError(f"Interface '{self.subject.__original_qualname__}' has no attribute '{attr}'.")
|
||||||
@@ -280,7 +295,31 @@ class Prophecy:
|
|||||||
call: Call = {"name": attr, "args": args, "kwargs": kwargs}
|
call: Call = {"name": attr, "args": args, "kwargs": kwargs}
|
||||||
# Ensure that signature is valid
|
# Ensure that signature is valid
|
||||||
getattr(self.subject, attr)(*args, **kwargs)
|
getattr(self.subject, attr)(*args, **kwargs)
|
||||||
key = json.dumps(call, sort_keys=True)
|
|
||||||
|
try:
|
||||||
|
key = self.serialize(call)
|
||||||
|
except TypeError as e:
|
||||||
|
# Serialization error will occur if unpredicted return value
|
||||||
|
# will be used as an argument to other call
|
||||||
|
msg = f"Failed to serialize call: '{call}'."
|
||||||
|
values = list(flatten(args)) + list(flatten(kwargs.values()))
|
||||||
|
prophecy = next((value for value in values if isinstance(value, Prophecy)), None)
|
||||||
|
if prophecy is None:
|
||||||
|
msg += "\nCouldn't find related Prophecy."
|
||||||
|
raise TypeError(msg) from e
|
||||||
|
|
||||||
|
msg += "\nPossibly due to unpredicted return value for some call."
|
||||||
|
calls_strs: list[str] = []
|
||||||
|
for call in reversed(prophecy.calls):
|
||||||
|
call_str = self.serialize(call)
|
||||||
|
if call_str in prophecy.return_values:
|
||||||
|
continue
|
||||||
|
calls_strs.append(f"- {call}")
|
||||||
|
if calls_strs:
|
||||||
|
msg += "\nSee the list of the recent calls without return values:\n"
|
||||||
|
msg += "\n".join(calls_strs)
|
||||||
|
raise TypeError(msg) from e
|
||||||
|
|
||||||
self.calls.append(call)
|
self.calls.append(call)
|
||||||
if key in self.return_values:
|
if key in self.return_values:
|
||||||
return self.return_values[key]
|
return self.return_values[key]
|
||||||
@@ -296,7 +335,8 @@ class Prophecy:
|
|||||||
|
|
||||||
def will_return(self, value: Any) -> Self:
|
def will_return(self, value: Any) -> Self:
|
||||||
"""Remember a return value for the last predicted call."""
|
"""Remember a return value for the last predicted call."""
|
||||||
key = json.dumps(self.should_call, sort_keys=True)
|
assert self.should_call
|
||||||
|
key = self.serialize(self.should_call)
|
||||||
self.return_values[key] = value
|
self.return_values[key] = value
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user