Coverage for src/pytest_cases/fixture__creation.py: 81%

53 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2024-04-04 21:17 +0000

1# Authors: Sylvain MARIE <sylvain.marie@se.com> 

2# + All contributors to <https://github.com/smarie/python-pytest-cases> 

3# 

4# License: 3-clause BSD, <https://github.com/smarie/python-pytest-cases/blob/master/LICENSE> 

5from __future__ import division 

6 

7from inspect import getmodule, currentframe 

8from warnings import warn 

9 

10try: 

11 # type hints, python 3+ 

12 from typing import Callable, Any, Union, Iterable # noqa 

13 from types import ModuleType # noqa 

14except ImportError: 

15 pass 

16 

17from .common_others import make_identifier 

18 

19 

20class ExistingFixtureNameError(ValueError): 

21 """ 

22 Raised by `add_fixture_to_callers_module` when a fixture already exists in a module 

23 """ 

24 def __init__(self, module, name, caller): 

25 self.module = module 

26 self.name = name 

27 self.caller = caller 

28 

29 def __str__(self): 

30 return "Symbol `%s` already exists in module %s and therefore a corresponding fixture can not be created by " \ 

31 "`%s`" % (self.name, self.module, self.caller) 

32 

33 

34RAISE = 0 

35WARN = 1 

36CHANGE = 2 

37 

38 

39def check_name_available(module, 

40 name, # type: str 

41 if_name_exists=RAISE, # type: int 

42 name_changer=None, # type: Callable 

43 caller=None, # type: Callable[[Any], Any] 

44 extra_forbidden_names=() # type: Iterable[str] 

45 ): 

46 """ 

47 Routine to check that a name is not already in dir(module) + extra_forbidden_names. 

48 The `if_name_exists` argument allows users to specify what happens if a name exists already. 

49 

50 `if_name_exists=CHANGE` allows users to ask for a new non-conflicting name to be found and returned. 

51 

52 :param module: a module or a class. dir(module) + extra_forbidden_names is used as a reference of forbidden names 

53 :param name: proposed name, to check against existent names in module 

54 :param if_name_exists: policy to apply if name already exists in dir(module) + extra_forbidden_names 

55 :param name_changer: an optional custom name changer function for new names to be generated 

56 :param caller: for warning / error messages. Something identifying the caller 

57 :param extra_forbidden_names: a reference list of additional forbidden names that can be provided, in addition to 

58 dir(module) 

59 :return: a name that might be different if policy was CHANGE 

60 """ 

61 new_name = make_identifier(name) 

62 if new_name != name: 

63 if if_name_exists is RAISE: 63 ↛ 64line 63 didn't jump to line 64, because the condition on line 63 was never true

64 raise ValueError("Proposed name is an invalid identifier: %s" % name) 

65 elif if_name_exists is WARN: 65 ↛ 66line 65 didn't jump to line 66, because the condition on line 65 was never true

66 warn("%s name was not a valid identifier, changed it to %s" % (name, new_name)) 

67 name = new_name 

68 

69 if name_changer is None: 

70 # default style for name changing. i starts with 1 

71 def name_changer(name, i): 

72 return name + '_%s' % i 

73 

74 ref_list = dir(module) + list(extra_forbidden_names) 

75 

76 if name in ref_list: 

77 if caller is None: 

78 caller = '' 

79 

80 # Name already exists: act according to policy 

81 if if_name_exists is RAISE: 81 ↛ 82line 81 didn't jump to line 82, because the condition on line 81 was never true

82 raise ExistingFixtureNameError(module, name, caller) 

83 

84 elif if_name_exists is WARN: 84 ↛ 85line 84 didn't jump to line 85, because the condition on line 84 was never true

85 warn("%s Overriding symbol %s in module %s" % (caller, name, module)) 

86 

87 elif if_name_exists is CHANGE: 87 ↛ 97line 87 didn't jump to line 97, because the condition on line 87 was never false

88 # find a non-used name in that module 

89 i = 1 

90 name2 = name_changer(name, i) 

91 while name2 in ref_list: 

92 i += 1 

93 name2 = name_changer(name, i) 

94 

95 name = name2 

96 else: 

97 raise ValueError("invalid value for `if_name_exists`: %s" % if_name_exists) 

98 

99 return name 

100 

101 

102def get_caller_module(frame_offset=1): 

103 # type: (...) -> ModuleType 

104 """ Return the module where the last frame belongs. 

105 

106 :param frame_offset: an alternate offset to look further up in the call stack 

107 :return: 

108 """ 

109 # grab context from the caller frame 

110 frame = _get_callerframe(offset=frame_offset) 

111 return getmodule(frame) 

112 

113 

114def _get_callerframe(offset=0): 

115 """ Return a frame in the call stack 

116 

117 :param offset: an alternate offset to look further up in the call stack 

118 :return: 

119 """ 

120 # inspect.stack is extremely slow, the fastest is sys._getframe or inspect.currentframe(). 

121 # See https://gist.github.com/JettJones/c236494013f22723c1822126df944b12 

122 # frame = sys._getframe(2 + offset) 

123 frame = currentframe() 

124 for _ in range(2 + offset): 

125 frame = frame.f_back 

126 return frame