Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/scipy/_lib/deprecation.py: 36%

92 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-03-22 06:44 +0000

1from inspect import Parameter, signature 

2import functools 

3import warnings 

4from importlib import import_module 

5 

6 

7__all__ = ["_deprecated"] 

8 

9 

10# Object to use as default value for arguments to be deprecated. This should 

11# be used over 'None' as the user could parse 'None' as a positional argument 

12_NoValue = object() 

13 

14def _sub_module_deprecation(*, sub_package, module, private_modules, all, 

15 attribute, correct_module=None): 

16 """Helper function for deprecating modules that are public but were 

17 intended to be private. 

18 

19 Parameters 

20 ---------- 

21 sub_package : str 

22 Subpackage the module belongs to eg. stats 

23 module : str 

24 Public but intended private module to deprecate 

25 private_modules : list 

26 Private replacement(s) for `module`; should contain the 

27 content of ``all``, possibly spread over several modules. 

28 all : list 

29 ``__all__`` belonging to `module` 

30 attribute : str 

31 The attribute in `module` being accessed 

32 correct_module : str, optional 

33 Module in `sub_package` that `attribute` should be imported from. 

34 Default is that `attribute` should be imported from ``scipy.sub_package``. 

35 """ 

36 if correct_module is not None: 

37 correct_import = f"scipy.{sub_package}.{correct_module}" 

38 else: 

39 correct_import = f"scipy.{sub_package}" 

40 

41 if attribute not in all: 

42 raise AttributeError( 

43 f"`scipy.{sub_package}.{module}` has no attribute `{attribute}`; " 

44 f"furthermore, `scipy.{sub_package}.{module}` is deprecated " 

45 f"and will be removed in SciPy 2.0.0." 

46 ) 

47 

48 attr = getattr(import_module(correct_import), attribute, None) 

49 

50 if attr is not None: 

51 message = ( 

52 f"Please import `{attribute}` from the `{correct_import}` namespace; " 

53 f"the `scipy.{sub_package}.{module}` namespace is deprecated " 

54 f"and will be removed in SciPy 2.0.0." 

55 ) 

56 else: 

57 message = ( 

58 f"`scipy.{sub_package}.{module}.{attribute}` is deprecated along with " 

59 f"the `scipy.{sub_package}.{module}` namespace. " 

60 f"`scipy.{sub_package}.{module}.{attribute}` will be removed " 

61 f"in SciPy 1.14.0, and the `scipy.{sub_package}.{module}` namespace " 

62 f"will be removed in SciPy 2.0.0." 

63 ) 

64 

65 warnings.warn(message, category=DeprecationWarning, stacklevel=3) 

66 

67 for module in private_modules: 

68 try: 

69 return getattr(import_module(f"scipy.{sub_package}.{module}"), attribute) 

70 except AttributeError as e: 

71 # still raise an error if the attribute isn't in any of the expected 

72 # private modules 

73 if module == private_modules[-1]: 

74 raise e 

75 continue 

76 

77 

78def _deprecated(msg, stacklevel=2): 

79 """Deprecate a function by emitting a warning on use.""" 

80 def wrap(fun): 

81 if isinstance(fun, type): 

82 warnings.warn( 

83 f"Trying to deprecate class {fun!r}", 

84 category=RuntimeWarning, stacklevel=2) 

85 return fun 

86 

87 @functools.wraps(fun) 

88 def call(*args, **kwargs): 

89 warnings.warn(msg, category=DeprecationWarning, 

90 stacklevel=stacklevel) 

91 return fun(*args, **kwargs) 

92 call.__doc__ = fun.__doc__ 

93 return call 

94 

95 return wrap 

96 

97 

98class _DeprecationHelperStr: 

99 """ 

100 Helper class used by deprecate_cython_api 

101 """ 

102 def __init__(self, content, message): 

103 self._content = content 

104 self._message = message 

105 

106 def __hash__(self): 

107 return hash(self._content) 

108 

109 def __eq__(self, other): 

110 res = (self._content == other) 

111 if res: 

112 warnings.warn(self._message, category=DeprecationWarning, 

113 stacklevel=2) 

114 return res 

115 

116 

117def deprecate_cython_api(module, routine_name, new_name=None, message=None): 

118 """ 

119 Deprecate an exported cdef function in a public Cython API module. 

120 

121 Only functions can be deprecated; typedefs etc. cannot. 

122 

123 Parameters 

124 ---------- 

125 module : module 

126 Public Cython API module (e.g. scipy.linalg.cython_blas). 

127 routine_name : str 

128 Name of the routine to deprecate. May also be a fused-type 

129 routine (in which case its all specializations are deprecated). 

130 new_name : str 

131 New name to include in the deprecation warning message 

132 message : str 

133 Additional text in the deprecation warning message 

134 

135 Examples 

136 -------- 

137 Usually, this function would be used in the top-level of the 

138 module ``.pyx`` file: 

139 

140 >>> from scipy._lib.deprecation import deprecate_cython_api 

141 >>> import scipy.linalg.cython_blas as mod 

142 >>> deprecate_cython_api(mod, "dgemm", "dgemm_new", 

143 ... message="Deprecated in Scipy 1.5.0") 

144 >>> del deprecate_cython_api, mod 

145 

146 After this, Cython modules that use the deprecated function emit a 

147 deprecation warning when they are imported. 

148 

149 """ 

150 old_name = f"{module.__name__}.{routine_name}" 

151 

152 if new_name is None: 

153 depdoc = "`%s` is deprecated!" % old_name 

154 else: 

155 depdoc = f"`{old_name}` is deprecated, use `{new_name}` instead!" 

156 

157 if message is not None: 

158 depdoc += "\n" + message 

159 

160 d = module.__pyx_capi__ 

161 

162 # Check if the function is a fused-type function with a mangled name 

163 j = 0 

164 has_fused = False 

165 while True: 

166 fused_name = f"__pyx_fuse_{j}{routine_name}" 

167 if fused_name in d: 

168 has_fused = True 

169 d[_DeprecationHelperStr(fused_name, depdoc)] = d.pop(fused_name) 

170 j += 1 

171 else: 

172 break 

173 

174 # If not, apply deprecation to the named routine 

175 if not has_fused: 

176 d[_DeprecationHelperStr(routine_name, depdoc)] = d.pop(routine_name) 

177 

178 

179# taken from scikit-learn, see 

180# https://github.com/scikit-learn/scikit-learn/blob/1.3.0/sklearn/utils/validation.py#L38 

181def _deprecate_positional_args(func=None, *, version=None): 

182 """Decorator for methods that issues warnings for positional arguments. 

183 

184 Using the keyword-only argument syntax in pep 3102, arguments after the 

185 * will issue a warning when passed as a positional argument. 

186 

187 Parameters 

188 ---------- 

189 func : callable, default=None 

190 Function to check arguments on. 

191 version : callable, default=None 

192 The version when positional arguments will result in error. 

193 """ 

194 if version is None: 

195 msg = "Need to specify a version where signature will be changed" 

196 raise ValueError(msg) 

197 

198 def _inner_deprecate_positional_args(f): 

199 sig = signature(f) 

200 kwonly_args = [] 

201 all_args = [] 

202 

203 for name, param in sig.parameters.items(): 

204 if param.kind == Parameter.POSITIONAL_OR_KEYWORD: 

205 all_args.append(name) 

206 elif param.kind == Parameter.KEYWORD_ONLY: 

207 kwonly_args.append(name) 

208 

209 @functools.wraps(f) 

210 def inner_f(*args, **kwargs): 

211 extra_args = len(args) - len(all_args) 

212 if extra_args <= 0: 

213 return f(*args, **kwargs) 

214 

215 # extra_args > 0 

216 args_msg = [ 

217 f"{name}={arg}" 

218 for name, arg in zip(kwonly_args[:extra_args], args[-extra_args:]) 

219 ] 

220 args_msg = ", ".join(args_msg) 

221 warnings.warn( 

222 ( 

223 f"You are passing {args_msg} as a positional argument. " 

224 "Please change your invocation to use keyword arguments. " 

225 f"From SciPy {version}, passing these as positional " 

226 "arguments will result in an error." 

227 ), 

228 DeprecationWarning, 

229 stacklevel=2, 

230 ) 

231 kwargs.update(zip(sig.parameters, args)) 

232 return f(**kwargs) 

233 

234 return inner_f 

235 

236 if func is not None: 

237 return _inner_deprecate_positional_args(func) 

238 

239 return _inner_deprecate_positional_args