Rough outline how it works: 1. Test run pass: - Remember calls (all calls should also have ``.should_be_called()`` after). - Remember predictions. - Associate return values with calls. 2. Core function pass: - Remember calls. - Use return values from the first pass.
| 284 | |
| 285 | |
| 286 | class Prophecy: |
| 287 | """ |
| 288 | Rough outline how it works: |
| 289 | 1. Test run pass: |
| 290 | - Remember calls (all calls should also have ``.should_be_called()`` after). |
| 291 | - Remember predictions. |
| 292 | - Associate return values with calls. |
| 293 | |
| 294 | 2. Core function pass: |
| 295 | - Remember calls. |
| 296 | - Use return values from the first pass. |
| 297 | |
| 298 | 3. Verification pass: |
| 299 | - Ensure all predicted calls actually happened. |
| 300 | """ |
| 301 | |
| 302 | subject: type |
| 303 | |
| 304 | def __init__(self, cls: type): |
| 305 | self.subject = cls |
| 306 | self.predictions: list[Prediction] = [] |
| 307 | self.calls: list[Call] = [] |
| 308 | self.return_values: dict[str, Any] = {} |
| 309 | self.should_call: Optional[Call] = None |
| 310 | |
| 311 | @staticmethod |
| 312 | def serialize(call: Call) -> str: |
| 313 | return json.dumps(call, sort_keys=True) |
| 314 | |
| 315 | def __repr__(self) -> str: |
| 316 | return f"<Prophecy for '{self.subject.__original_qualname__}'>" |
| 317 | |
| 318 | def __getattr__(self, attr: str): |
| 319 | if not hasattr(self.subject, attr): |
| 320 | raise AttributeError(f"Interface '{self.subject.__original_qualname__}' has no attribute '{attr}'.") |
| 321 | |
| 322 | # It also returns `Any` but it only happens during `subject.xxx` call. |
| 323 | def decorate(*args: Any, **kwargs: Any) -> Self: |
| 324 | """Remember a call.""" |
| 325 | call: Call = {"name": attr, "args": args, "kwargs": kwargs} |
| 326 | # Ensure that signature is valid |
| 327 | getattr(self.subject, attr)(*args, **kwargs) |
| 328 | |
| 329 | try: |
| 330 | key = self.serialize(call) |
| 331 | except TypeError as e: |
| 332 | # Serialization error will occur if unpredicted return value |
| 333 | # will be used as an argument to other call |
| 334 | msg = f"Failed to serialize call: '{call}'." |
| 335 | values = list(flatten(args)) + list(flatten(kwargs.values())) |
| 336 | prophecy = next((value for value in values if isinstance(value, Prophecy)), None) |
| 337 | if prophecy is None: |
| 338 | msg += "\nCouldn't find related Prophecy." |
| 339 | raise TypeError(msg) from e |
| 340 | |
| 341 | msg += "\nPossibly due to unpredicted return value for some call." |
| 342 | calls_strs: list[str] = [] |
| 343 | for call in reversed(prophecy.calls): |
no outgoing calls
no test coverage detected