flexmock 0.12.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
flexmock/_api.py ADDED
@@ -0,0 +1,1533 @@
1
+ """Flexmock public API."""
2
+
3
+ # pylint: disable=too-many-lines
4
+ import inspect
5
+ import re
6
+ import sys
7
+ import types
8
+ from types import BuiltinMethodType, TracebackType
9
+ from typing import Any, Callable, Dict, Iterator, List, NoReturn, Optional, Tuple, Type, Union
10
+
11
+ from flexmock.exceptions import (
12
+ CallOrderError,
13
+ ExceptionClassError,
14
+ ExceptionMessageError,
15
+ FlexmockError,
16
+ MethodCallError,
17
+ MethodSignatureError,
18
+ MockBuiltinError,
19
+ StateError,
20
+ )
21
+
22
+ AT_LEAST = "at least"
23
+ AT_MOST = "at most"
24
+ EXACTLY = "exactly"
25
+ SPECIAL_METHODS = (classmethod, staticmethod)
26
+ UPDATED_ATTRS = ["should_receive", "should_call", "new_instances"]
27
+ DEFAULT_CLASS_ATTRIBUTES = [attr for attr in dir(type) if attr not in dir(type("", (object,), {}))]
28
+ # Fix Python 3.6 does not have re.Pattern type
29
+ RE_TYPE = type(re.compile(""))
30
+
31
+
32
+ class ReturnValue:
33
+ """ReturnValue"""
34
+
35
+ def __init__(
36
+ self,
37
+ value: Optional[Any] = None,
38
+ raises: Optional[Union[Type[BaseException], BaseException]] = None,
39
+ ) -> None:
40
+ self.value = value
41
+ self.raises = raises
42
+
43
+ def __str__(self) -> str:
44
+ if self.raises:
45
+ return f"{self.raises}({_arg_to_str(self.value)})"
46
+ if not isinstance(self.value, tuple):
47
+ return str(_arg_to_str(self.value))
48
+ if len(self.value) == 1:
49
+ return str(_arg_to_str(self.value[0]))
50
+ values = ", ".join([_arg_to_str(x) for x in self.value])
51
+ return f"({values})"
52
+
53
+
54
+ class Mock:
55
+ """Fake object class returned by the `flexmock()` function."""
56
+
57
+ def __init__(self, **kwargs: Any) -> None:
58
+ """Mock constructor.
59
+
60
+ Args:
61
+ **kwargs: dict of attribute/value pairs used to initialize the mock
62
+ object.
63
+ """
64
+ self._object: Any = self
65
+ for attr, value in kwargs.items():
66
+ if isinstance(value, property):
67
+ setattr(self.__class__, attr, value)
68
+ else:
69
+ setattr(self, attr, value)
70
+
71
+ def __enter__(self) -> Any:
72
+ return self._object
73
+
74
+ def __exit__(
75
+ self,
76
+ exc_type: Optional[Type[BaseException]],
77
+ exc_value: Optional[BaseException],
78
+ traceback: Optional[TracebackType],
79
+ ) -> None:
80
+ pass
81
+
82
+ def __call__(self, *args: Any, **kwargs: Any) -> "Mock":
83
+ """Make mocks callable with and without parentheses.
84
+
85
+ If `Mock` is not callable, it is difficult to mock attributes that
86
+ should work with and without parentheses.
87
+ """
88
+ return self
89
+
90
+ def __iter__(self) -> Iterator[Any]:
91
+ """Makes the mock object iterable.
92
+
93
+ Call the instance's version of __iter__ if available, otherwise yield self.
94
+ """
95
+ if (
96
+ hasattr(self, "__dict__")
97
+ and isinstance(self.__dict__, dict)
98
+ and "__iter__" in self.__dict__
99
+ ):
100
+ yield from self.__dict__["__iter__"](self)
101
+ else:
102
+ yield self
103
+
104
+ def should_receive(self, name: str) -> "Expectation":
105
+ """Replaces the specified attribute with a fake.
106
+
107
+ Args:
108
+ name: Name of the attribute to replace.
109
+
110
+ Returns:
111
+ Expectation object which can be used to modify the expectations
112
+ on the fake attribute.
113
+
114
+ Examples:
115
+ >>> flexmock(plane).should_receive("fly").and_return("vooosh!").once()
116
+ <flexmock._api.Expectation object at ...>
117
+ >>> plane.fly()
118
+ 'vooosh!'
119
+ """
120
+ if name in UPDATED_ATTRS:
121
+ raise FlexmockError("unable to replace flexmock methods")
122
+
123
+ chained_methods = None
124
+ if "." in name:
125
+ name, chained_methods = name.split(".", 1)
126
+ name = self._update_name_if_mangled(name)
127
+ self._ensure_object_has_named_attribute(name)
128
+
129
+ if chained_methods:
130
+ if not isinstance(self._object, Mock) and not hasattr(
131
+ getattr(self._object, name), "__call__"
132
+ ):
133
+ # Create a partial mock if the given name is callable
134
+ # this allows chaining attributes
135
+ return_value = _create_partial_mock(getattr(self._object, name))
136
+ else:
137
+ return_value = Mock()
138
+ self._create_expectation(name, return_value)
139
+ return return_value.should_receive(chained_methods)
140
+
141
+ return self._create_expectation(name)
142
+
143
+ def _update_name_if_mangled(self, name: str) -> str:
144
+ """This allows flexmock to mock methods with name mangling."""
145
+ if name.startswith("__") and not name.endswith("__") and not inspect.ismodule(self._object):
146
+ class_name: str
147
+ if inspect.isclass(self._object):
148
+ class_name = self._object.__name__
149
+ else:
150
+ class_name = self._object.__class__.__name__
151
+ name = f"_{class_name.lstrip('_')}__{name.lstrip('_')}"
152
+ return name
153
+
154
+ def _ensure_object_has_named_attribute(self, name: str) -> None:
155
+ if not isinstance(self._object, Mock) and not self._hasattr(self._object, name):
156
+ if hasattr(self._object, "__name__"):
157
+ obj_name = self._object.__name__
158
+ else:
159
+ obj_name = str(self._object)
160
+ raise FlexmockError(f"{obj_name} does not have attribute '{name}'")
161
+
162
+ def _hasattr(self, obj: Any, name: str) -> bool:
163
+ """Ensure hasattr checks don't create side-effects for properties."""
164
+ if not inspect.isclass(obj) and hasattr(obj, "__dict__") and name not in obj.__dict__:
165
+ if name in DEFAULT_CLASS_ATTRIBUTES:
166
+ return False # avoid false positives for things like __call__
167
+ return hasattr(obj.__class__, name)
168
+ return hasattr(obj, name)
169
+
170
+ def should_call(self, name: str) -> "Expectation":
171
+ """Creates a spy.
172
+
173
+ This means that the original method will be called rather than the fake
174
+ version. However, we can still keep track of how many times it is called
175
+ and with what arguments, and apply expectations accordingly.
176
+
177
+ `should_call` is meaningless/not allowed for non-callable attributes.
178
+
179
+ Args:
180
+ name: Name of the method to spy.
181
+
182
+ Returns:
183
+ Expectation object.
184
+
185
+ Examples:
186
+ >>> flexmock(plane).should_call("repair").once()
187
+ <flexmock._api.Expectation object at ...>
188
+ >>> plane.repair("wing")
189
+ """
190
+ if isinstance(self._object, Mock) and not hasattr(self._object, name):
191
+ raise FlexmockError(
192
+ f"Mock object does not have attribute '{name}'. "
193
+ f'Did you mean to call should_receive("{name}") instead?'
194
+ )
195
+ expectation = self.should_receive(name)
196
+ return expectation.replace_with(expectation.__dict__["_original"])
197
+
198
+ def new_instances(self, *args: Any) -> "Expectation":
199
+ """Overrides `__new__` method on a class to return custom objects.
200
+
201
+ Alias for `should_receive('__new__').and_return(args).one_by_one()`.
202
+
203
+ Args:
204
+ *args: Objects to return on each successive call to `__new__`.
205
+
206
+ Returns:
207
+ Expectation object.
208
+
209
+ Examples:
210
+
211
+ >>> fake_plane = flexmock(model="fake")
212
+ >>> flexmock(Plane).new_instances(fake_plane)
213
+ <flexmock._api.Expectation object at ...>
214
+ >>> Plane().model
215
+ 'fake'
216
+
217
+ It is also possible to return different fake objects in a sequence:
218
+
219
+ >>> fake_plane1 = flexmock(model="fake1")
220
+ >>> fake_plane2 = flexmock(model="fake2")
221
+ >>> flexmock(Plane).new_instances(fake_plane1, fake_plane2)
222
+ <flexmock._api.Expectation object at ...>
223
+ >>> Plane().model
224
+ 'fake1'
225
+ >>> Plane().model
226
+ 'fake2'
227
+ """
228
+ if inspect.isclass(self._object):
229
+ return self.should_receive("__new__").and_return(args).one_by_one()
230
+ raise FlexmockError("new_instances can only be called on a class mock")
231
+
232
+ def _create_expectation(self, name: str, return_value: Optional[Any] = None) -> "Expectation":
233
+ expectation = self._get_or_create_expectation(name, return_value)
234
+ FlexmockContainer.add_expectation(self, expectation)
235
+
236
+ if _isproperty(self._object, name):
237
+ self._update_property(expectation, name)
238
+ elif (
239
+ isinstance(self._object, Mock)
240
+ or hasattr(getattr(self._object, name), "__call__")
241
+ or inspect.isclass(getattr(self._object, name))
242
+ ):
243
+ self._update_method(expectation, name)
244
+ else:
245
+ self._update_attribute(expectation, name, return_value)
246
+ return expectation
247
+
248
+ def _get_or_create_expectation(
249
+ self, name: str, return_value: Optional[Any] = None
250
+ ) -> "Expectation":
251
+ saved_expectations = FlexmockContainer.get_expectations_with_name(self, name)
252
+ if saved_expectations:
253
+ # If there is already an expectation for the same name, get the
254
+ # original object from the FIRST saved expectation.
255
+ return Expectation(
256
+ self._object,
257
+ name=name,
258
+ return_value=return_value,
259
+ original=saved_expectations[0].__dict__.get("_original"),
260
+ method_type=saved_expectations[0].__dict__.get("_method_type"),
261
+ )
262
+ return Expectation(self._object, name=name, return_value=return_value)
263
+
264
+ def _create_placeholder_mock_for_proper_teardown(
265
+ self, obj: Any, name: str, original: Any
266
+ ) -> None:
267
+ """Ensures that the given function is replaced on teardown."""
268
+ mock = Mock()
269
+ mock._object = obj
270
+ expectation = Expectation(obj, name=name, original=original)
271
+ FlexmockContainer.add_expectation(mock, expectation)
272
+
273
+ def _update_method(self, expectation: "Expectation", name: str) -> None:
274
+ method_instance = self._create_mock_method(name)
275
+ if self._hasattr(self._object, name) and not hasattr(expectation, "_original"):
276
+ expectation._update_original(name, self._object)
277
+ expectation._method_type = self._get_method_type(name, expectation._original)
278
+ if expectation._method_type in SPECIAL_METHODS:
279
+ expectation._original_function = getattr(self._object, name)
280
+ if not inspect.isclass(self._object) or expectation._method_type in SPECIAL_METHODS:
281
+ method_instance = types.MethodType(method_instance, self._object)
282
+ expectation._local_override = _setattr(self._object, name, method_instance)
283
+ if (
284
+ expectation._local_override
285
+ and not inspect.isclass(self._object)
286
+ and not isinstance(self._object, Mock)
287
+ and hasattr(self._object.__class__, name)
288
+ ):
289
+ self._update_class_for_magic_builtins(name)
290
+
291
+ def _get_method_type(self, name: str, method: Callable[..., Any]) -> Any:
292
+ """Get method type of the original method.
293
+
294
+ Method type is saved because after mocking the base class, it is difficult to determine
295
+ the original method type.
296
+ """
297
+ method_type = self._get_saved_method_type(name, method)
298
+ if method_type is not None:
299
+ return method_type
300
+ if _is_class_method(method, name):
301
+ method_type = classmethod
302
+ elif _is_static_method(self._object, name):
303
+ method_type = staticmethod
304
+ else:
305
+ method_type = type(method)
306
+ setattr(self._object, f"{name}__flexmock__method_type", method_type)
307
+ return method_type
308
+
309
+ def _get_saved_method_type(self, name: str, method: Callable[..., Any]) -> Optional[Any]:
310
+ """Check method type of the original method if it was saved to the class or base class."""
311
+ bound_to = getattr(method, "__self__", None)
312
+ if bound_to is not None and inspect.isclass(bound_to):
313
+ # Check if the method type was saved in a base class
314
+ for cls in inspect.getmro(bound_to):
315
+ method_type = vars(cls).get(f"{name}__flexmock__method_type")
316
+ if method_type:
317
+ return method_type
318
+ return None
319
+
320
+ def _update_class_for_magic_builtins(self, name: str) -> None:
321
+ """Fixes method resolution order for built-in methods.
322
+
323
+ Replacing magic builtins on instances has no effect as the one attached
324
+ to the class takes precedence. To work around it, we update the class'
325
+ method to check if the instance in question has one in its own __dict__
326
+ and call that instead.
327
+ """
328
+ if not (name.startswith("__") and name.endswith("__") and len(name) > 4):
329
+ return
330
+ original = getattr(self._object.__class__, name)
331
+
332
+ def updated(self: Any, *kargs: Any, **kwargs: Any) -> Any:
333
+ if (
334
+ hasattr(self, "__dict__")
335
+ and isinstance(self.__dict__, dict)
336
+ and name in self.__dict__
337
+ ):
338
+ return self.__dict__[name](*kargs, **kwargs)
339
+ return original(self, *kargs, **kwargs)
340
+
341
+ setattr(self._object.__class__, name, updated)
342
+ if updated.__code__ != original.__code__:
343
+ self._create_placeholder_mock_for_proper_teardown(
344
+ self._object.__class__, name, original
345
+ )
346
+
347
+ def _update_attribute(
348
+ self, expectation: "Expectation", name: str, return_value: Optional[Any] = None
349
+ ) -> None:
350
+ expectation._callable = False
351
+ if self._hasattr(self._object, name) and not hasattr(expectation, "_original"):
352
+ expectation._update_original(name, self._object)
353
+ expectation._local_override = _setattr(self._object, name, return_value)
354
+
355
+ def _update_property(self, expectation: "Expectation", name: str) -> None:
356
+ new_name = f"_flexmock__{name}"
357
+ obj = self._object
358
+ if not inspect.isclass(obj):
359
+ obj = obj.__class__
360
+
361
+ expectation._callable = False
362
+ original = getattr(obj, name)
363
+
364
+ @property # type: ignore
365
+ def updated(self: Any) -> Any:
366
+ if (
367
+ hasattr(self, "__dict__")
368
+ and isinstance(self.__dict__, dict)
369
+ and name in self.__dict__
370
+ ):
371
+ return self.__dict__[name]
372
+ # Return original for instances that are not mocked
373
+ return getattr(self, new_name)
374
+
375
+ setattr(obj, name, updated)
376
+ if not hasattr(obj, new_name):
377
+ # don't try to double update
378
+ FlexmockContainer.add_teardown_property(obj, new_name)
379
+ setattr(obj, new_name, original)
380
+ self._create_placeholder_mock_for_proper_teardown(obj, name, original)
381
+
382
+ def _create_mock_method(self, name: str) -> Callable[..., Any]:
383
+ def _handle_exception_matching(expectation: Expectation) -> None:
384
+ # pylint: disable=misplaced-bare-raise
385
+ return_values = expectation._return_values
386
+ if return_values:
387
+ raised, instance = sys.exc_info()[:2]
388
+ assert raised, "no exception was raised"
389
+ message = str(instance)
390
+ expected = return_values[0].raises
391
+ if not expected:
392
+ raise
393
+ args = return_values[0].value
394
+ if inspect.isclass(expected):
395
+ assert isinstance(args, dict)
396
+ expected_instance = expected(*args["kargs"], **args["kwargs"])
397
+ expected_message = str(expected_instance)
398
+ if expected is not raised and expected not in raised.__bases__:
399
+ raise ExceptionClassError(
400
+ f"Raised exception for call {expectation._name} "
401
+ "did not match expectation:\n"
402
+ f" Expected:\t{expected}\n"
403
+ f" Raised:\t{raised}"
404
+ )
405
+ if args["kargs"] and isinstance(args["kargs"][0], RE_TYPE):
406
+ if not args["kargs"][0].search(message):
407
+ raise ExceptionMessageError(
408
+ f"Error message mismatch with raised {expected.__name__}:\n"
409
+ f" Expected pattern:\n\t/{args['kargs'][0].pattern}/\n"
410
+ f" Received message:\n\t'{message}'"
411
+ )
412
+ elif expected_message and expected_message != message:
413
+ raise (
414
+ ExceptionMessageError(
415
+ f"Error message mismatch with raised {expected.__name__}:\n"
416
+ f" Expected message:\n\t'{message}'\n"
417
+ f" Received message:\n\t'{expected_message}'"
418
+ )
419
+ )
420
+ elif expected is not raised:
421
+ raise ExceptionClassError(
422
+ f"Raised exception for call {expectation._name} "
423
+ f"did not match expectation:\n"
424
+ f" Expected:\t{repr(expected)}\n"
425
+ f" Raised:\t{raised}\n\n"
426
+ "Did you try to call and_raise with an instance?\n"
427
+ 'Instead of and_raise(Exception("arg")), try and_raise(Exception, "arg")'
428
+ )
429
+ else:
430
+ raise
431
+
432
+ def match_return_values(expected: Any, received: Any) -> bool:
433
+ if not isinstance(expected, tuple):
434
+ expected = (expected,)
435
+ if not isinstance(received, tuple):
436
+ received = (received,)
437
+ if len(received) != len(expected):
438
+ return False
439
+ for i, val in enumerate(received):
440
+ if not _arguments_match(val, expected[i]):
441
+ return False
442
+ return True
443
+
444
+ def pass_thru(
445
+ expectation: Expectation, runtime_self: Any, *kargs: Any, **kwargs: Any
446
+ ) -> Any:
447
+ return_values = None
448
+ try:
449
+ original = expectation._original
450
+ _mock = expectation._mock
451
+ if inspect.isclass(_mock):
452
+ if expectation._method_type in SPECIAL_METHODS:
453
+ original = expectation._original_function
454
+ return_values = original(*kargs, **kwargs)
455
+ else:
456
+ return_values = original(runtime_self, *kargs, **kwargs)
457
+ else:
458
+ return_values = original(*kargs, **kwargs)
459
+ except Exception:
460
+ return _handle_exception_matching(expectation)
461
+ expected_values = expectation._return_values
462
+ if expected_values and not match_return_values(expected_values[0].value, return_values):
463
+ expected_value = expected_values[0].value
464
+ # Display strings with quotes in the error message
465
+ if isinstance(return_values, str):
466
+ return_values = repr(return_values)
467
+ if isinstance(expected_value, str):
468
+ expected_value = repr(expected_value)
469
+ raise (
470
+ MethodSignatureError(
471
+ f"Returned values for call {expectation._name} did not match expectation:\n"
472
+ f" Expected:\t{expected_value}\n"
473
+ f" Returned:\t{return_values}"
474
+ )
475
+ )
476
+ return return_values
477
+
478
+ def _handle_matched_expectation(
479
+ expectation: Expectation, runtime_self: Any, *kargs: Any, **kwargs: Any
480
+ ) -> Any:
481
+ if not expectation._runnable():
482
+ raise StateError(
483
+ f"{name} expected to be called when {expectation._get_runnable()} is True"
484
+ )
485
+ expectation._times_called += 1
486
+ expectation._verify(final=False)
487
+ _pass_thru = expectation._pass_thru
488
+ _replace_with = expectation._replace_with
489
+ if _pass_thru:
490
+ return pass_thru(expectation, runtime_self, *kargs, **kwargs)
491
+ if _replace_with:
492
+ return _replace_with(*kargs, **kwargs)
493
+ return_values = expectation._return_values
494
+ if return_values:
495
+ return_value = return_values[0]
496
+ del return_values[0]
497
+ return_values.append(return_value)
498
+ else:
499
+ return_value = ReturnValue()
500
+ if return_value.raises:
501
+ if inspect.isclass(return_value.raises):
502
+ args = return_value.value
503
+ assert isinstance(args, dict)
504
+ raise return_value.raises(*args["kargs"], **args["kwargs"])
505
+ raise return_value.raises
506
+ return return_value.value
507
+
508
+ def mock_method(runtime_self: Any, *kargs: Any, **kwargs: Any) -> Any:
509
+ arguments = {"kargs": kargs, "kwargs": kwargs}
510
+ expectation = FlexmockContainer.get_flexmock_expectation(self, name, arguments)
511
+ if expectation:
512
+ return _handle_matched_expectation(expectation, runtime_self, *kargs, **kwargs)
513
+ # inform the user which expectation(s) for the method were _not_ matched
514
+ saved_expectations = reversed(FlexmockContainer.get_expectations_with_name(self, name))
515
+ error_msg = (
516
+ f"Arguments for call {name} did not match expectations:\n"
517
+ f" Received call:\t{_format_args(name, arguments)}\n"
518
+ )
519
+ if saved_expectations:
520
+ error_msg += "\n".join(
521
+ f" Expected call[{index}]:\t{_format_args(name, expectation._args)}"
522
+ for index, expectation in enumerate(saved_expectations, 1)
523
+ )
524
+ raise MethodSignatureError(error_msg)
525
+
526
+ return mock_method
527
+
528
+
529
+ def flexmock_teardown() -> None:
530
+ """Performs flexmock-specific teardown tasks."""
531
+ saved = {}
532
+ instances = []
533
+ classes = []
534
+ for mock_object, expectations in FlexmockContainer.flexmock_objects.items():
535
+ saved[mock_object] = expectations[:]
536
+ # Reset the expectations in reversed order to ensure everything is restored to
537
+ # its original form even if setting consequent expectations on the same object
538
+ # operates on top of previous expectation and not on original object.
539
+ for expectation in reversed(expectations):
540
+ expectation._reset()
541
+ for expectation in expectations:
542
+ # Remove method type attributes set by flexmock. This needs to be done after
543
+ # resetting all the expectations because method type is needed in expectation teardown.
544
+ if inspect.isclass(mock_object) or hasattr(mock_object, "__class__"):
545
+ try:
546
+ delattr(mock_object._object, f"{expectation._name}__flexmock__method_type")
547
+ except (AttributeError, TypeError):
548
+ pass
549
+ for mock in saved:
550
+ obj = mock._object
551
+ if not isinstance(obj, Mock) and not inspect.isclass(obj):
552
+ instances.append(obj)
553
+ if inspect.isclass(obj):
554
+ classes.append(obj)
555
+ for obj in instances + classes:
556
+ for attr in UPDATED_ATTRS:
557
+ try:
558
+ obj_dict = obj.__dict__
559
+ if obj_dict[attr].__code__ is Mock.__dict__[attr].__code__:
560
+ del obj_dict[attr]
561
+ except Exception:
562
+ try:
563
+ if getattr(obj, attr).__code__ is Mock.__dict__[attr].__code__:
564
+ delattr(obj, attr)
565
+ except AttributeError:
566
+ pass
567
+ FlexmockContainer.teardown_properties()
568
+ FlexmockContainer.reset()
569
+
570
+ # make sure this is done last to keep exceptions here from breaking
571
+ # any of the previous steps that cleanup all the changes
572
+ for mock_object, expectations in saved.items():
573
+ for expectation in expectations:
574
+ expectation._verify()
575
+
576
+
577
+ class Expectation:
578
+ """Holds expectations about methods.
579
+
580
+ The information contained in the Expectation object includes method name,
581
+ its argument list, return values, and any exceptions that the method might
582
+ raise.
583
+ """
584
+
585
+ def __init__(
586
+ self,
587
+ mock: Mock,
588
+ name: Optional[str] = None,
589
+ return_value: Optional[Any] = None,
590
+ original: Optional[Any] = None,
591
+ method_type: Optional[Any] = None,
592
+ ) -> None:
593
+ if original is not None:
594
+ self._original = original
595
+
596
+ self._name = name
597
+ self._times_called: int = 0
598
+ self._modifier: str = EXACTLY
599
+ self._args: Optional[Dict[str, Any]] = None
600
+ self._method_type = method_type
601
+ self._argspec: Optional[inspect.FullArgSpec] = None
602
+ self._return_values = [ReturnValue(return_value)] if return_value is not None else []
603
+ self._replace_with: Optional[Callable[..., Any]] = None
604
+ self._original_function: Optional[Callable[..., Any]] = None
605
+ self._expected_calls: Dict[str, Optional[int]] = {
606
+ EXACTLY: None,
607
+ AT_LEAST: None,
608
+ AT_MOST: None,
609
+ }
610
+ self._runnable: Callable[..., bool] = lambda: True
611
+ self._mock = mock
612
+ self._pass_thru = False
613
+ self._ordered = False
614
+ self._one_by_one = False
615
+ self._verified = False
616
+ self._callable = True
617
+ self._local_override = False
618
+ # Remove in 1.0. Issue #96
619
+ self._called_deprecated_property = False
620
+
621
+ def __str__(self) -> str:
622
+ args = _format_args(str(self._name), self._args)
623
+ return_values = ", ".join(str(x) for x in self._return_values)
624
+ return f"{args} -> ({return_values})"
625
+
626
+ def __getattribute__(self, name: str) -> Any:
627
+ # Workaround to raise an error if once, twice, or never are called
628
+ # without parenthesis.
629
+ if name in ("once", "twice", "never", "mock"):
630
+ self._called_deprecated_property = True
631
+ return object.__getattribute__(self, name)
632
+
633
+ def _get_runnable(self) -> str:
634
+ """Ugly hack to get the name of when() condition from the source code."""
635
+ name = "condition"
636
+ try:
637
+ source = inspect.getsource(self._runnable)
638
+ if "when(" in source:
639
+ name = source.split("when(")[1].split(")")[0]
640
+ elif "def " in source:
641
+ name = source.split("def ")[1].split("(")[0]
642
+ except Exception:
643
+ # couldn't get the source, oh well
644
+ pass
645
+ return name
646
+
647
+ def _verify_signature_match(self, *kargs: Any, **kwargs: Any) -> None:
648
+ if isinstance(self._mock, Mock):
649
+ return # no sense in enforcing this for fake objects
650
+ allowed = self._argspec
651
+ args_len = len(allowed.args)
652
+
653
+ # self is the first expected argument
654
+ has_self = allowed.args and allowed.args[0] == "self"
655
+ # Builtin methods take `self` as the first argument but `inspect.ismethod` returns False
656
+ # so we need to check for them explicitly
657
+ is_builtin_method = isinstance(self._original, BuiltinMethodType) and has_self
658
+ # Methods take `self` if not a staticmethod
659
+ is_method = inspect.ismethod(self._original) and self._method_type is not staticmethod
660
+ # Class init takes `self`
661
+ is_class = inspect.isclass(self._original)
662
+ # When calling class methods or instance methods on a class method takes `cls`
663
+ is_class_method = (
664
+ inspect.isfunction(self._original)
665
+ and inspect.isclass(self._mock)
666
+ and self._method_type is not staticmethod
667
+ )
668
+ if is_builtin_method or is_method or is_class or is_class_method:
669
+ # Do not count `self` or `cls`.
670
+ args_len -= 1
671
+
672
+ minimum = args_len - (allowed.defaults and len(allowed.defaults) or 0)
673
+ maximum = None
674
+ if allowed.varargs is None and allowed.varkw is None:
675
+ maximum = args_len
676
+ total_positional = len(kargs + tuple(a for a in kwargs if a in allowed.args))
677
+ named_optionals = [
678
+ a
679
+ for a in kwargs
680
+ if allowed.defaults
681
+ if a in allowed.args[len(allowed.args) - len(allowed.defaults) :]
682
+ ]
683
+ if allowed.defaults and total_positional == minimum and named_optionals:
684
+ minimum += len(named_optionals)
685
+ if total_positional < minimum:
686
+ arguments = "argument" if minimum == 1 else "arguments"
687
+ raise MethodSignatureError(
688
+ f"{self._name} requires at least {minimum} {arguments}, "
689
+ f"expectation provided {total_positional}"
690
+ )
691
+ if maximum is not None and total_positional > maximum:
692
+ arguments = "argument" if maximum == 1 else "arguments"
693
+ raise MethodSignatureError(
694
+ f"{self._name} requires at most {maximum} {arguments}, "
695
+ f"expectation provided {total_positional}"
696
+ )
697
+ if args_len == len(kargs) and any(a for a in kwargs if a in allowed.args):
698
+ given_args = [a for a in kwargs if a in allowed.args]
699
+ arguments = "argument" if len(given_args) == 1 else "arguments"
700
+ raise MethodSignatureError(
701
+ f"{given_args} already given as positional {arguments} to {self._name}"
702
+ )
703
+ if not allowed.varkw and any(
704
+ a for a in kwargs if a not in allowed.args + allowed.kwonlyargs
705
+ ):
706
+ invalid_arg = [a for a in kwargs if a not in allowed.args + allowed.kwonlyargs][0]
707
+ raise MethodSignatureError(
708
+ f"{invalid_arg} is not a valid keyword argument to {self._name}"
709
+ )
710
+ # check that kwonlyargs that don't have default value specified are provided
711
+ required_kwonlyargs = [
712
+ a for a in allowed.kwonlyargs if a not in (allowed.kwonlydefaults or {})
713
+ ]
714
+ missing_kwonlyargs = [a for a in required_kwonlyargs if a not in kwargs]
715
+ if missing_kwonlyargs:
716
+ arguments = "argument" if len(missing_kwonlyargs) == 1 else "arguments"
717
+ missing_args = '", "'.join(missing_kwonlyargs)
718
+ raise MethodSignatureError(
719
+ f'{self._name} requires keyword-only {arguments} "{missing_args}"'
720
+ )
721
+
722
+ def _update_original(self, name: str, obj: Any) -> None:
723
+ if hasattr(obj, "__dict__") and name in obj.__dict__:
724
+ self._original = obj.__dict__[name]
725
+ else:
726
+ self._original = getattr(obj, name)
727
+ self._update_argspec()
728
+
729
+ def _update_argspec(self) -> None:
730
+ original = self.__dict__.get("_original")
731
+ if original:
732
+ try:
733
+ self._argspec = inspect.getfullargspec(original)
734
+ except TypeError:
735
+ # built-in function: fall back to stupid processing and hope the
736
+ # builtins don't change signature
737
+ pass
738
+
739
+ def _normalize_named_args(self, *kargs: Any, **kwargs: Any) -> Dict[str, Any]:
740
+ argspec = self._argspec
741
+ default = {"kargs": kargs, "kwargs": kwargs}
742
+ if not argspec:
743
+ return default
744
+ ret: Dict[str, Any] = {"kargs": (), "kwargs": kwargs}
745
+ if inspect.ismethod(self._original):
746
+ args = argspec.args[1:]
747
+ else:
748
+ args = argspec.args
749
+ for i, arg in enumerate(kargs):
750
+ if len(args) <= i:
751
+ return default
752
+ ret["kwargs"][args[i]] = arg
753
+ return ret
754
+
755
+ def __raise(self, exception: Type[BaseException], message: str) -> NoReturn:
756
+ """Safe internal raise implementation.
757
+
758
+ In case we're patching builtins, it's important to reset the
759
+ expectation before raising any exceptions or else things like
760
+ open() might be stubbed out and the resulting runner errors are very
761
+ difficult to diagnose.
762
+ """
763
+ self._reset()
764
+ raise exception(message)
765
+
766
+ def _match_args(self, given_args: Any) -> bool:
767
+ """Check if the set of given arguments matches this expectation."""
768
+ expected_args = self._args
769
+ given_args = self._normalize_named_args(*given_args["kargs"], **given_args["kwargs"])
770
+ if expected_args == given_args or expected_args is None:
771
+ return True
772
+ if (
773
+ len(given_args["kargs"]) != len(expected_args["kargs"])
774
+ or len(given_args["kwargs"]) != len(expected_args["kwargs"])
775
+ or (sorted(given_args["kwargs"].keys()) != sorted(expected_args["kwargs"].keys()))
776
+ ):
777
+ return False
778
+ for i, arg in enumerate(given_args["kargs"]):
779
+ if not _arguments_match(arg, expected_args["kargs"][i]):
780
+ return False
781
+ for key, value in given_args["kwargs"].items():
782
+ if not _arguments_match(value, expected_args["kwargs"][key]):
783
+ return False
784
+ return True
785
+
786
+ def mock(self) -> Mock:
787
+ """Return the mock associated with this expectation.
788
+
789
+ Returns:
790
+ Mock associated with this expectation.
791
+
792
+ Examples:
793
+ >>> plane = flexmock(plane).should_receive("fly").mock()
794
+ """
795
+ self._called_deprecated_property = False
796
+ return self._mock
797
+
798
+ def with_args(self, *args: Any, **kwargs: Any) -> "Expectation":
799
+ """Override the arguments used to match this expectation's method.
800
+
801
+ Args:
802
+ *args: Positional arguments.
803
+ **kwargs: Keyword arguments.
804
+
805
+ Returns:
806
+ Self, i.e. can be chained with other Expectation methods.
807
+
808
+ Examples:
809
+ Match calls with no arguments:
810
+
811
+ >>> flexmock(plane).should_receive("fly").with_args()
812
+ <flexmock._api.Expectation object at ...>
813
+
814
+ Match a single argument:
815
+
816
+ >>> flexmock(plane).should_receive("fly").with_args("east")
817
+ <flexmock._api.Expectation object at ...>
818
+
819
+ Match keyword arguments:
820
+
821
+ >>> flexmock(plane).should_receive("fly").with_args("up", destination="Oslo")
822
+ <flexmock._api.Expectation object at ...>
823
+
824
+ Match argument type:
825
+
826
+ >>> flexmock(plane).should_receive("fly").with_args(str)
827
+ <flexmock._api.Expectation object at ...>
828
+
829
+ Match a string using a compiled regular expression:
830
+
831
+ >>> regex = re.compile("^(up|down)$")
832
+ >>> flexmock(plane).should_receive("fly").with_args(regex)
833
+ <flexmock._api.Expectation object at ...>
834
+ """
835
+ if not self._callable:
836
+ self.__raise(FlexmockError, "can't use with_args() with attribute stubs")
837
+ self._update_argspec()
838
+ if self._argspec:
839
+ # do this outside try block as TypeError is way too general and catches
840
+ # unrelated errors in the verify signature code
841
+ self._verify_signature_match(*args, **kwargs)
842
+ self._args = self._normalize_named_args(*args, **kwargs)
843
+ else:
844
+ self._args = {"kargs": args, "kwargs": kwargs}
845
+ return self
846
+
847
+ def and_return(self, *values: Any) -> "Expectation":
848
+ """Override the return value of this expectation's method.
849
+
850
+ When `and_return` is given multiple times, each value provided is returned
851
+ on successive invocations of the method. It is also possible to mix
852
+ `and_return` with `and_raise` in the same manner to alternate between returning
853
+ a value and raising and exception on different method invocations.
854
+
855
+ When combined with the `one_by_one` modifier, value is treated as a list of
856
+ values to be returned in the order specified by successive calls to this
857
+ method rather than a single list to be returned each time.
858
+
859
+ Args:
860
+ *values: Optional list of return values, defaults to `None` if not given.
861
+
862
+ Returns:
863
+ Self, i.e. can be chained with other Expectation methods.
864
+
865
+ Examples:
866
+ Provide return values for mocks:
867
+
868
+ >>> flexmock(plane).should_receive("land").and_return("landed!").once()
869
+ <flexmock._api.Expectation object at ...>
870
+ >>> plane.land()
871
+ 'landed!'
872
+
873
+ Match specific return values with spies:
874
+
875
+ >>> flexmock(plane).should_call("passenger_count").and_return(3)
876
+ <flexmock._api.Expectation object at ...>
877
+ >>> plane.passenger_count()
878
+ 3
879
+ """
880
+ if not values:
881
+ value = None
882
+ elif len(values) == 1:
883
+ value = values[0]
884
+ else:
885
+ value = values
886
+
887
+ if not self._callable:
888
+ _setattr(self._mock, str(self._name), value)
889
+ return self
890
+
891
+ return_values = self._return_values
892
+ if not self._one_by_one:
893
+ value = ReturnValue(value)
894
+ return_values.append(value)
895
+ else:
896
+ try:
897
+ return_values.extend([ReturnValue(v) for v in value]) # type: ignore
898
+ except TypeError:
899
+ return_values.append(ReturnValue(value))
900
+ return self
901
+
902
+ def times(self, number: int) -> "Expectation":
903
+ """Number of times this expectation's method is expected to be called.
904
+
905
+ There are also 3 aliases for the `times()` method:
906
+
907
+ - `once()` -> `times(1)`
908
+ - `twice()` -> `times(2)`
909
+ - `never()` -> `times(0)`
910
+
911
+ Args:
912
+ number: Expected call count.
913
+
914
+ Returns:
915
+ Self, i.e. can be chained with other Expectation methods.
916
+
917
+ Examples:
918
+ >>> flexmock(plane).should_receive("fly").times(1)
919
+ <flexmock._api.Expectation object at ...>
920
+ >>> plane.fly()
921
+
922
+ >>> flexmock(plane).should_call("land").times(2)
923
+ <flexmock._api.Expectation object at ...>
924
+ >>> plane.land()
925
+ >>> plane.land()
926
+ """
927
+ if not self._callable:
928
+ self.__raise(FlexmockError, "can't use times() with attribute stubs")
929
+ expected_calls = self._expected_calls
930
+ modifier = self._modifier
931
+ expected_calls[modifier] = number
932
+ return self
933
+
934
+ def once(self) -> "Expectation":
935
+ """Expect expectation's method to be called once.
936
+
937
+ Alias for `times(1)`.
938
+
939
+ Returns:
940
+ Self, i.e. can be chained with other Expectation methods.
941
+
942
+ Examples:
943
+ >>> flexmock(plane).should_receive("land").once()
944
+ <flexmock._api.Expectation object at ...>
945
+ >>> plane.land()
946
+ """
947
+ self._called_deprecated_property = False
948
+ return self.times(1)
949
+
950
+ def twice(self) -> "Expectation":
951
+ """Expect expectation's method to be called twice.
952
+
953
+ Alias for `times(2)`.
954
+
955
+ Returns:
956
+ Self, i.e. can be chained with other Expectation methods.
957
+
958
+ Examples:
959
+ >>> flexmock(plane).should_receive("fly").twice()
960
+ <flexmock._api.Expectation object at ...>
961
+ >>> plane.fly()
962
+ >>> plane.fly()
963
+ """
964
+ self._called_deprecated_property = False
965
+ return self.times(2)
966
+
967
+ def never(self) -> "Expectation":
968
+ """Expect expectation's method to never be called.
969
+
970
+ Alias for `times(0)`.
971
+
972
+ Returns:
973
+ Self, i.e. can be chained with other Expectation methods.
974
+
975
+ Examples:
976
+ >>> flexmock(plane).should_receive("crash").never()
977
+ <flexmock._api.Expectation object at ...>
978
+ >>> plane.crash()
979
+ Traceback (most recent call last):
980
+ ...
981
+ flexmock.exceptions.MethodCallError: crash() expected to be called...
982
+ """
983
+ self._called_deprecated_property = False
984
+ return self.times(0)
985
+
986
+ def one_by_one(self) -> "Expectation":
987
+ """Modifies the return value to be treated as a list of return values.
988
+
989
+ Each value in the list is returned on successive invocations of the method.
990
+
991
+ Returns:
992
+ Self, i.e. can be chained with other Expectation methods.
993
+
994
+ Examples:
995
+ >>> flexmock(plane).should_receive("pilot").and_return("pilot1", "pilot2").one_by_one()
996
+ <flexmock._api.Expectation object at ...>
997
+ >>> plane.pilot()
998
+ 'pilot1'
999
+ >>> plane.pilot()
1000
+ 'pilot2'
1001
+ """
1002
+ if not self._callable:
1003
+ self.__raise(FlexmockError, "can't use one_by_one() with attribute stubs")
1004
+ if not self._one_by_one:
1005
+ self._one_by_one = True
1006
+ return_values = self._return_values
1007
+ saved_values = return_values[:]
1008
+ self._return_values = return_values = []
1009
+ for value in saved_values:
1010
+ try:
1011
+ for val in value.value: # type: ignore
1012
+ return_values.append(ReturnValue(val))
1013
+ except TypeError:
1014
+ return_values.append(value)
1015
+ return self
1016
+
1017
+ def at_least(self) -> "Expectation":
1018
+ """Modifies the associated `times()` expectation.
1019
+
1020
+ When given, an exception will only be raised if the method is called less
1021
+ than `times()` specified. Does nothing if `times()` is not given.
1022
+
1023
+ Returns:
1024
+ Self, i.e. can be chained with other Expectation methods.
1025
+
1026
+ Examples:
1027
+ >>> flexmock(plane).should_receive("fly").at_least().once()
1028
+ <flexmock._api.Expectation object at ...>
1029
+ >>> plane.fly("east")
1030
+ """
1031
+ if not self._callable:
1032
+ self.__raise(FlexmockError, "can't use at_least() with attribute stubs")
1033
+ expected_calls = self._expected_calls
1034
+ modifier = self._modifier
1035
+ if expected_calls[AT_LEAST] is not None or modifier == AT_LEAST:
1036
+ self.__raise(FlexmockError, "cannot use at_least modifier twice")
1037
+ if modifier == AT_MOST and expected_calls[AT_MOST] is None:
1038
+ self.__raise(FlexmockError, "cannot use at_least with at_most unset")
1039
+ self._modifier = AT_LEAST
1040
+ return self
1041
+
1042
+ def at_most(self) -> "Expectation":
1043
+ """Modifies the associated `times()` expectation.
1044
+
1045
+ When given, an exception will only be raised if the method is called more
1046
+ than `times()` specified. Does nothing if `times()` is not given.
1047
+
1048
+ Returns:
1049
+ Self, i.e. can be chained with other Expectation methods.
1050
+
1051
+ Examples:
1052
+ >>> flexmock(plane).should_receive("land").at_most().once()
1053
+ <flexmock._api.Expectation object at ...>
1054
+ >>> plane.land()
1055
+ >>> plane.land()
1056
+ Traceback (most recent call last):
1057
+ ...
1058
+ flexmock.exceptions.MethodCallError: land() expected to be called at most...
1059
+ """
1060
+ if not self._callable:
1061
+ self.__raise(FlexmockError, "can't use at_most() with attribute stubs")
1062
+ expected_calls = self._expected_calls
1063
+ modifier = self._modifier
1064
+ if expected_calls[AT_MOST] is not None or modifier == AT_MOST:
1065
+ self.__raise(FlexmockError, "cannot use at_most modifier twice")
1066
+ if modifier == AT_LEAST and expected_calls[AT_LEAST] is None:
1067
+ self.__raise(FlexmockError, "cannot use at_most with at_least unset")
1068
+ self._modifier = AT_MOST
1069
+ return self
1070
+
1071
+ def ordered(self) -> "Expectation":
1072
+ """Makes the expectation respect the order of `should_receive` statements.
1073
+
1074
+ An exception will be raised if methods are called out of order, determined
1075
+ by order of `should_receive` calls in the test.
1076
+
1077
+ Returns:
1078
+ Self, i.e. can be chained with other Expectation methods.
1079
+
1080
+ Examples:
1081
+ >>> flexmock(plane).should_receive("fly").with_args("east").ordered()
1082
+ <flexmock._api.Expectation object at ...>
1083
+ >>> flexmock(plane).should_receive("fly").with_args("west").ordered()
1084
+ <flexmock._api.Expectation object at ...>
1085
+ >>> plane.fly("west")
1086
+ Traceback (most recent call last):
1087
+ ...
1088
+ flexmock.exceptions.CallOrderError: fly("west") called before...
1089
+ """
1090
+ if not self._callable:
1091
+ self.__raise(FlexmockError, "can't use ordered() with attribute stubs")
1092
+ self._ordered = True
1093
+ FlexmockContainer.ordered.append(self)
1094
+ return self
1095
+
1096
+ def when(self, func: Callable[..., Any]) -> "Expectation":
1097
+ """Sets an outside resource to be checked when asserting if a method
1098
+ should be called.
1099
+
1100
+ Args:
1101
+ func: Function that indicates if the mocked or spied method should
1102
+ have been called.
1103
+
1104
+ Returns:
1105
+ Self, i.e. can be chained with other Expectation methods.
1106
+
1107
+ Examples:
1108
+ >>> flexmock(plane).should_receive("land").when(lambda: plane.is_flying)
1109
+ <flexmock._api.Expectation object at ...>
1110
+ >>> plane.is_flying
1111
+ True
1112
+ >>> plane.land()
1113
+ >>> plane.is_flying = False
1114
+ >>> plane.land()
1115
+ Traceback (most recent call last):
1116
+ ...
1117
+ flexmock.exceptions.StateError: land expected to be called when...
1118
+ """
1119
+ if not self._callable:
1120
+ self.__raise(FlexmockError, "can't use when() with attribute stubs")
1121
+ if not hasattr(func, "__call__"):
1122
+ self.__raise(FlexmockError, "when() parameter must be callable")
1123
+ self._runnable = func
1124
+ return self
1125
+
1126
+ def and_raise(
1127
+ self, exception: Union[Type[BaseException], BaseException], *args: Any, **kwargs: Any
1128
+ ) -> "Expectation":
1129
+ """Specifies the exception to be raised when this expectation is met.
1130
+
1131
+ Args:
1132
+ exception: Exception class.
1133
+ *args: Positional arguments to pass to the exception.
1134
+ **kwargs: Keyword arguments to pass to the exception.
1135
+
1136
+ Returns:
1137
+ Self, i.e. can be chained with other Expectation methods.
1138
+
1139
+ Examples:
1140
+ Make a mocked method raise an exception instead of returning a value:
1141
+
1142
+ >>> flexmock(plane).should_receive("fly").and_raise(BadWeatherException)
1143
+ <flexmock._api.Expectation object at ...>
1144
+ >>> plane.fly()
1145
+ Traceback (most recent call last):
1146
+ ...
1147
+ BadWeatherException
1148
+
1149
+ Make a spy to verify that a specific exception is raised:
1150
+
1151
+ >>> flexmock(plane).should_call("repair").and_raise(RuntimeError, "err msg")
1152
+ <flexmock._api.Expectation object at ...>
1153
+ """
1154
+ if not self._callable:
1155
+ self.__raise(FlexmockError, "can't use and_raise() with attribute stubs")
1156
+ if inspect.isclass(exception):
1157
+ try:
1158
+ exception(*args, **kwargs)
1159
+ except TypeError:
1160
+ self.__raise(
1161
+ FlexmockError, f"can't initialize {exception} with the given arguments"
1162
+ )
1163
+ kargs = {"kargs": args, "kwargs": kwargs}
1164
+ return_values = self._return_values
1165
+ return_values.append(ReturnValue(raises=exception, value=kargs))
1166
+ return self
1167
+
1168
+ def replace_with(self, function: Callable[..., Any]) -> "Expectation":
1169
+ """Gives a function to run instead of the mocked out one.
1170
+
1171
+ Args:
1172
+ function: A callable that is used to replace the original function.
1173
+
1174
+ Returns:
1175
+ Self, i.e. can be chained with other Expectation methods.
1176
+
1177
+ Examples:
1178
+ >>> flexmock(plane).should_receive("set_speed").replace_with(lambda x: x == 5)
1179
+ <flexmock._api.Expectation object at ...>
1180
+ >>> plane.set_speed(5)
1181
+ True
1182
+ >>> plane.set_speed(4)
1183
+ False
1184
+ """
1185
+ if not self._callable:
1186
+ self.__raise(FlexmockError, "can't use replace_with() with attribute/property stubs")
1187
+ replace_with = self._replace_with
1188
+ original = self.__dict__.get("_original")
1189
+ if replace_with:
1190
+ self.__raise(FlexmockError, "replace_with cannot be specified twice")
1191
+ if function == original:
1192
+ self._pass_thru = True
1193
+ self._replace_with = function
1194
+ return self
1195
+
1196
+ def and_yield(self, *args: Any) -> "Expectation":
1197
+ """Specifies the list of items to be yielded on successive method calls.
1198
+
1199
+ In effect, the mocked object becomes a generator.
1200
+
1201
+ Args:
1202
+ *args: Items to be yielded on successive method calls.
1203
+
1204
+ Returns:
1205
+ Self, i.e. can be chained with other Expectation methods.
1206
+
1207
+ Examples:
1208
+ >>> flexmock(plane).should_receive("flight_log").and_yield("fly", "land")
1209
+ <flexmock._api.Expectation object at ...>
1210
+ >>> log = plane.flight_log()
1211
+ >>> next(log)
1212
+ 'fly'
1213
+ >>> next(log)
1214
+ 'land'
1215
+ """
1216
+ if not self._callable:
1217
+ self.__raise(FlexmockError, "can't use and_yield() with attribute stubs")
1218
+ return self.and_return(iter(args))
1219
+
1220
+ def _verify(self, final: bool = True) -> None:
1221
+ """Verify that this expectation has been met.
1222
+
1223
+ Args:
1224
+ final: True if no further calls to this method expected
1225
+ (skip checking at_least expectations when False).
1226
+
1227
+ Raises:
1228
+ MethodCallError Exception
1229
+ """
1230
+ failed, message = self._verify_number_of_calls(final)
1231
+ if failed and not self._verified:
1232
+ self._verified = True
1233
+ self.__raise(
1234
+ MethodCallError,
1235
+ (
1236
+ f"{_format_args(str(self._name), self._args)} expected to be called "
1237
+ f"{message}, called {self._times_called} "
1238
+ f"{'time' if self._times_called == 1 else 'times'}"
1239
+ ),
1240
+ )
1241
+
1242
+ def _verify_number_of_calls(self, final: bool) -> Tuple[bool, str]:
1243
+ if self._called_deprecated_property:
1244
+ raise FlexmockError(
1245
+ "Calling once, twice, never, or mock without parentheses has been deprecated"
1246
+ )
1247
+ failed = False
1248
+ message = ""
1249
+ called_exactly = self._expected_calls[EXACTLY]
1250
+ called_at_least = self._expected_calls[AT_LEAST]
1251
+ called_at_most = self._expected_calls[AT_MOST]
1252
+ times_called = self._times_called
1253
+ if called_exactly is not None:
1254
+ message = f"exactly {called_exactly}"
1255
+ if final:
1256
+ if times_called != called_exactly:
1257
+ failed = True
1258
+ else:
1259
+ if times_called > called_exactly:
1260
+ failed = True
1261
+ message += " time" if called_exactly == 1 else " times"
1262
+ else:
1263
+ if final and called_at_least is not None:
1264
+ message = f"at least {called_at_least}"
1265
+ if times_called < called_at_least:
1266
+ failed = True
1267
+ message += " time" if called_at_least == 1 else " times"
1268
+ if called_at_most is not None:
1269
+ if message:
1270
+ message += " and "
1271
+ message += f"at most {called_at_most}"
1272
+ if times_called > called_at_most:
1273
+ failed = True
1274
+ message += " time" if called_at_most == 1 else " times"
1275
+ return failed, message
1276
+
1277
+ def _reset(self) -> None:
1278
+ """Returns the methods overridden by this expectation to their originals."""
1279
+ _mock = self._mock
1280
+ if not isinstance(_mock, Mock):
1281
+ original = self.__dict__.get("_original")
1282
+ if original:
1283
+ # name may be unicode but pypy demands dict keys to be str
1284
+ name = str(self._name)
1285
+ if hasattr(_mock, "__dict__") and name in _mock.__dict__ and self._local_override:
1286
+ delattr(_mock, name)
1287
+ elif (
1288
+ hasattr(_mock, "__dict__")
1289
+ and name in _mock.__dict__
1290
+ and isinstance(_mock.__dict__, dict)
1291
+ ):
1292
+ _mock.__dict__[name] = original
1293
+ else:
1294
+ setattr(_mock, name, original)
1295
+ del self
1296
+
1297
+
1298
+ class FlexmockContainer:
1299
+ """Holds global hash of object/expectation mappings."""
1300
+
1301
+ flexmock_objects: Dict[Mock, List[Expectation]] = {}
1302
+ properties: Dict[Any, List[str]] = {}
1303
+ ordered: List[Expectation] = []
1304
+ last: Optional[Expectation] = None
1305
+
1306
+ @classmethod
1307
+ def reset(cls) -> None:
1308
+ """Reset flexmock state."""
1309
+ cls.ordered = []
1310
+ cls.last = None
1311
+ cls.flexmock_objects = {}
1312
+ cls.properties = {}
1313
+
1314
+ @classmethod
1315
+ def get_flexmock_expectation(
1316
+ cls, obj: Mock, name: str, args: Optional[Any] = None
1317
+ ) -> Optional[Expectation]:
1318
+ """Retrieves an existing matching expectation."""
1319
+ if args is None:
1320
+ args = {"kargs": (), "kwargs": {}}
1321
+ if not isinstance(args, dict):
1322
+ args = {"kargs": args, "kwargs": {}}
1323
+ if not isinstance(args["kargs"], tuple):
1324
+ args["kargs"] = (args["kargs"],)
1325
+ found = None
1326
+ for expectation in reversed(cls.flexmock_objects[obj]):
1327
+ if expectation._name == name and expectation._match_args(args):
1328
+ if expectation in cls.ordered or not expectation._ordered and not found:
1329
+ found = expectation
1330
+ if found and found._ordered:
1331
+ cls._verify_call_order(found, args)
1332
+ return found
1333
+
1334
+ @classmethod
1335
+ def _verify_call_order(cls, expectation: Expectation, args: Dict[str, Any]) -> None:
1336
+ next_method = cls.ordered.pop(0)
1337
+ cls.last = next_method
1338
+ if expectation is not next_method:
1339
+ raise CallOrderError(
1340
+ f"{_format_args(str(expectation._name), args)} called before "
1341
+ f"{_format_args(str(next_method._name), next_method._args)}"
1342
+ )
1343
+
1344
+ @classmethod
1345
+ def add_expectation(cls, obj: Mock, expectation: Expectation) -> None:
1346
+ """Add expectation."""
1347
+ if obj in cls.flexmock_objects:
1348
+ cls.flexmock_objects[obj].append(expectation)
1349
+ else:
1350
+ cls.flexmock_objects[obj] = [expectation]
1351
+
1352
+ @classmethod
1353
+ def get_expectations_with_name(cls, obj: Mock, name: str) -> List[Expectation]:
1354
+ """Get all expectations for given name."""
1355
+ return [x for x in FlexmockContainer.flexmock_objects.get(obj, []) if x._name == name]
1356
+
1357
+ @classmethod
1358
+ def add_teardown_property(cls, obj: Any, name: str) -> None:
1359
+ """Add teardown property."""
1360
+ if obj in cls.properties:
1361
+ cls.properties[obj].append(name)
1362
+ else:
1363
+ cls.properties[obj] = [name]
1364
+
1365
+ @classmethod
1366
+ def teardown_properties(cls) -> None:
1367
+ """Teardown properties."""
1368
+ for obj, names in cls.properties.items():
1369
+ for name in names:
1370
+ delattr(obj, name)
1371
+
1372
+
1373
+ def flexmock(spec: Optional[Any] = None, **kwargs: Any) -> Mock:
1374
+ """Main entry point into the flexmock API.
1375
+
1376
+ This function is used to either generate a new fake object or take
1377
+ an existing object (or class or module) and use it as a basis for
1378
+ a partial mock. In case of a partial mock, the passed in object
1379
+ is modified to support basic Mock class functionality making
1380
+ it unnecessary to make successive `flexmock()` calls on the same
1381
+ objects to generate new expectations.
1382
+
1383
+ It is safe to call `flexmock()` on the same object again, flexmock will
1384
+ detect when an object has already been partially mocked and return it each
1385
+ time.
1386
+
1387
+ Args:
1388
+ spec: Object, class, or module to mock.
1389
+ **kwargs: Method or return value pairs to attach to the object.
1390
+
1391
+ Returns:
1392
+ Mock object if no spec is provided. Otherwise return the spec object.
1393
+
1394
+ Examples:
1395
+ >>> mock = flexmock(Plane)
1396
+ >>> mock.should_receive("fly")
1397
+ <flexmock._api.Expectation object at ...>
1398
+ """
1399
+ if spec is not None:
1400
+ return _create_partial_mock(spec, **kwargs)
1401
+ # use this intermediate class to attach properties
1402
+ klass = type("MockClass", (Mock,), {})
1403
+ return klass(**kwargs) # type: ignore
1404
+
1405
+
1406
+ def _arg_to_str(arg: Any) -> str:
1407
+ if isinstance(arg, RE_TYPE):
1408
+ return f"/{arg.pattern}/"
1409
+ if isinstance(arg, str):
1410
+ return f'"{arg}"'
1411
+ return f"{arg}"
1412
+
1413
+
1414
+ def _format_args(name: str, arguments: Optional[Dict[str, Any]]) -> str:
1415
+ if arguments is None:
1416
+ arguments = {"kargs": (), "kwargs": {}}
1417
+ kargs = ", ".join(_arg_to_str(arg) for arg in arguments["kargs"])
1418
+ kwargs = ", ".join(f"{k}={_arg_to_str(v)}" for k, v in arguments["kwargs"].items())
1419
+ if kargs and kwargs:
1420
+ args = f"{kargs}, {kwargs}"
1421
+ else:
1422
+ args = f"{kargs}{kwargs}"
1423
+ return f"{name}({args})"
1424
+
1425
+
1426
+ def _create_partial_mock(obj_or_class: Any, **kwargs: Any) -> Mock:
1427
+ """Create partial mock."""
1428
+ matches = [x for x in FlexmockContainer.flexmock_objects if x._object is obj_or_class]
1429
+ if matches:
1430
+ mock = matches[0]
1431
+ else:
1432
+ mock = Mock()
1433
+ mock._object = obj_or_class
1434
+ for name, return_value in kwargs.items():
1435
+ if hasattr(return_value, "__call__"):
1436
+ mock.should_receive(name).replace_with(return_value)
1437
+ else:
1438
+ mock.should_receive(name).and_return(return_value)
1439
+ if not matches:
1440
+ FlexmockContainer.add_expectation(mock, Expectation(obj_or_class))
1441
+ if _attach_flexmock_methods(mock, Mock, obj_or_class) and not inspect.isclass(mock._object):
1442
+ mock = mock._object
1443
+ return mock
1444
+
1445
+
1446
+ def _attach_flexmock_methods(mock: Mock, flexmock_class: Type[Mock], obj: Any) -> bool:
1447
+ try:
1448
+ for attr in UPDATED_ATTRS:
1449
+ if hasattr(obj, attr):
1450
+ if getattr(obj, attr).__code__ is not getattr(flexmock_class, attr).__code__:
1451
+ return False
1452
+ for attr in UPDATED_ATTRS:
1453
+ _setattr(obj, attr, getattr(mock, attr))
1454
+ except TypeError as exc:
1455
+ raise MockBuiltinError(
1456
+ "Python does not allow you to mock builtin objects or modules. "
1457
+ "Consider wrapping it in a class you can mock instead"
1458
+ ) from exc
1459
+ except AttributeError as exc:
1460
+ raise MockBuiltinError(
1461
+ "Python does not allow you to mock instances of builtin objects. "
1462
+ "Consider wrapping it in a class you can mock instead"
1463
+ ) from exc
1464
+ return True
1465
+
1466
+
1467
+ def _arguments_match(arg: Any, expected_arg: Any) -> bool:
1468
+ if expected_arg == arg:
1469
+ return True
1470
+ if inspect.isclass(expected_arg) and isinstance(arg, expected_arg):
1471
+ return True
1472
+ if isinstance(expected_arg, RE_TYPE) and expected_arg.search(arg):
1473
+ return True
1474
+ return False
1475
+
1476
+
1477
+ def _setattr(obj: Any, name: str, value: Any) -> bool:
1478
+ """Ensure we use local __dict__ where possible."""
1479
+ local_override = False
1480
+ if hasattr(obj, "__dict__") and isinstance(obj.__dict__, dict):
1481
+ if name not in obj.__dict__:
1482
+ # Overriding attribute locally on an instance.
1483
+ local_override = True
1484
+ obj.__dict__[name] = value
1485
+ else:
1486
+ if inspect.isclass(obj) and not vars(obj).get(name):
1487
+ # Overriding derived attribute locally on a child class.
1488
+ local_override = True
1489
+ setattr(obj, name, value)
1490
+ return local_override
1491
+
1492
+
1493
+ def _isproperty(obj: Any, name: str) -> bool:
1494
+ if isinstance(obj, Mock):
1495
+ return False
1496
+ if not inspect.isclass(obj) and hasattr(obj, "__dict__") and name not in obj.__dict__:
1497
+ attr = getattr(obj.__class__, name)
1498
+ if isinstance(attr, property):
1499
+ return True
1500
+ elif inspect.isclass(obj):
1501
+ attr = getattr(obj, name)
1502
+ if isinstance(attr, property):
1503
+ return True
1504
+ return False
1505
+
1506
+
1507
+ def _is_class_method(method: Callable[..., Any], name: str) -> bool:
1508
+ """Check if a method is a classmethod.
1509
+
1510
+ This function checks all the classes in the class method resolution in order
1511
+ to get the correct result for derived methods as well.
1512
+ """
1513
+ bound_to = getattr(method, "__self__", None)
1514
+ if not inspect.isclass(bound_to):
1515
+ return False
1516
+ for cls in inspect.getmro(bound_to):
1517
+ descriptor = vars(cls).get(name)
1518
+ if descriptor is not None:
1519
+ return isinstance(descriptor, classmethod)
1520
+ return False
1521
+
1522
+
1523
+ def _is_static_method(obj: Any, name: str) -> bool:
1524
+ try:
1525
+ return isinstance(inspect.getattr_static(obj, name), staticmethod)
1526
+ except AttributeError:
1527
+ # AttributeError is raised when mocking a proxied object
1528
+ if hasattr(obj, "__mro__"):
1529
+ for cls in inspect.getmro(obj):
1530
+ descriptor = vars(cls).get(name)
1531
+ if descriptor is not None:
1532
+ return isinstance(descriptor, staticmethod)
1533
+ return False