Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/sqlalchemy/util/deprecations.py: 90%

162 statements  

« prev     ^ index     » next       coverage.py v7.0.1, created at 2022-12-25 06:11 +0000

1# util/deprecations.py 

2# Copyright (C) 2005-2022 the SQLAlchemy authors and contributors 

3# <see AUTHORS file> 

4# 

5# This module is part of SQLAlchemy and is released under 

6# the MIT License: https://www.opensource.org/licenses/mit-license.php 

7 

8"""Helpers related to deprecation of functions, methods, classes, other 

9functionality.""" 

10 

11import os 

12import re 

13 

14from . import compat 

15from .langhelpers import _hash_limit_string 

16from .langhelpers import _warnings_warn 

17from .langhelpers import decorator 

18from .langhelpers import inject_docstring_text 

19from .langhelpers import inject_param_text 

20from .. import exc 

21 

22 

23SQLALCHEMY_WARN_20 = False 

24 

25if os.getenv("SQLALCHEMY_WARN_20", "false").lower() in ("true", "yes", "1"): 

26 SQLALCHEMY_WARN_20 = True 

27 

28 

29def _warn_with_version(msg, version, type_, stacklevel, code=None): 

30 if ( 

31 issubclass(type_, exc.Base20DeprecationWarning) 

32 and not SQLALCHEMY_WARN_20 

33 ): 

34 return 

35 

36 warn = type_(msg, code=code) 

37 warn.deprecated_since = version 

38 

39 _warnings_warn(warn, stacklevel=stacklevel + 1) 

40 

41 

42def warn_deprecated(msg, version, stacklevel=3, code=None): 

43 _warn_with_version( 

44 msg, version, exc.SADeprecationWarning, stacklevel, code=code 

45 ) 

46 

47 

48def warn_deprecated_limited(msg, args, version, stacklevel=3, code=None): 

49 """Issue a deprecation warning with a parameterized string, 

50 limiting the number of registrations. 

51 

52 """ 

53 if args: 

54 msg = _hash_limit_string(msg, 10, args) 

55 _warn_with_version( 

56 msg, version, exc.SADeprecationWarning, stacklevel, code=code 

57 ) 

58 

59 

60def warn_deprecated_20(msg, stacklevel=3, code=None): 

61 

62 _warn_with_version( 

63 msg, 

64 exc.RemovedIn20Warning.deprecated_since, 

65 exc.RemovedIn20Warning, 

66 stacklevel, 

67 code=code, 

68 ) 

69 

70 

71def deprecated_cls(version, message, constructor="__init__"): 

72 header = ".. deprecated:: %s %s" % (version, (message or "")) 

73 

74 def decorate(cls): 

75 return _decorate_cls_with_warning( 

76 cls, 

77 constructor, 

78 exc.SADeprecationWarning, 

79 message % dict(func=constructor), 

80 version, 

81 header, 

82 ) 

83 

84 return decorate 

85 

86 

87def deprecated_20_cls( 

88 clsname, alternative=None, constructor="__init__", becomes_legacy=False 

89): 

90 message = ( 

91 ".. deprecated:: 1.4 The %s class is considered legacy as of the " 

92 "1.x series of SQLAlchemy and %s in 2.0." 

93 % ( 

94 clsname, 

95 "will be removed" 

96 if not becomes_legacy 

97 else "becomes a legacy construct", 

98 ) 

99 ) 

100 

101 if alternative: 

102 message += " " + alternative 

103 

104 if becomes_legacy: 

105 warning_cls = exc.LegacyAPIWarning 

106 else: 

107 warning_cls = exc.RemovedIn20Warning 

108 

109 def decorate(cls): 

110 return _decorate_cls_with_warning( 

111 cls, 

112 constructor, 

113 warning_cls, 

114 message, 

115 warning_cls.deprecated_since, 

116 message, 

117 ) 

118 

119 return decorate 

120 

121 

122def deprecated( 

123 version, 

124 message=None, 

125 add_deprecation_to_docstring=True, 

126 warning=None, 

127 enable_warnings=True, 

128): 

129 """Decorates a function and issues a deprecation warning on use. 

130 

131 :param version: 

132 Issue version in the warning. 

133 

134 :param message: 

135 If provided, issue message in the warning. A sensible default 

136 is used if not provided. 

137 

138 :param add_deprecation_to_docstring: 

139 Default True. If False, the wrapped function's __doc__ is left 

140 as-is. If True, the 'message' is prepended to the docs if 

141 provided, or sensible default if message is omitted. 

142 

143 """ 

144 

145 # nothing is deprecated "since" 2.0 at this time. All "removed in 2.0" 

146 # should emit the RemovedIn20Warning, but messaging should be expressed 

147 # in terms of "deprecated since 1.4". 

148 

149 if version == "2.0": 

150 if warning is None: 

151 warning = exc.RemovedIn20Warning 

152 version = "1.4" 

153 if add_deprecation_to_docstring: 

154 header = ".. deprecated:: %s %s" % ( 

155 version, 

156 (message or ""), 

157 ) 

158 else: 

159 header = None 

160 

161 if message is None: 

162 message = "Call to deprecated function %(func)s" 

163 

164 if warning is None: 

165 warning = exc.SADeprecationWarning 

166 

167 if warning is not exc.RemovedIn20Warning: 

168 message += " (deprecated since: %s)" % version 

169 

170 def decorate(fn): 

171 return _decorate_with_warning( 

172 fn, 

173 warning, 

174 message % dict(func=fn.__name__), 

175 version, 

176 header, 

177 enable_warnings=enable_warnings, 

178 ) 

179 

180 return decorate 

181 

182 

183def moved_20(message, **kw): 

184 return deprecated( 

185 "2.0", message=message, warning=exc.MovedIn20Warning, **kw 

186 ) 

187 

188 

189def deprecated_20(api_name, alternative=None, becomes_legacy=False, **kw): 

190 type_reg = re.match("^:(attr|func|meth):", api_name) 

191 if type_reg: 

192 type_ = {"attr": "attribute", "func": "function", "meth": "method"}[ 

193 type_reg.group(1) 

194 ] 

195 else: 

196 type_ = "construct" 

197 message = ( 

198 "The %s %s is considered legacy as of the " 

199 "1.x series of SQLAlchemy and %s in 2.0." 

200 % ( 

201 api_name, 

202 type_, 

203 "will be removed" 

204 if not becomes_legacy 

205 else "becomes a legacy construct", 

206 ) 

207 ) 

208 

209 if ":attr:" in api_name: 

210 attribute_ok = kw.pop("warn_on_attribute_access", False) 

211 if not attribute_ok: 

212 assert kw.get("enable_warnings") is False, ( 

213 "attribute %s will emit a warning on read access. " 

214 "If you *really* want this, " 

215 "add warn_on_attribute_access=True. Otherwise please add " 

216 "enable_warnings=False." % api_name 

217 ) 

218 

219 if alternative: 

220 message += " " + alternative 

221 

222 if becomes_legacy: 

223 warning_cls = exc.LegacyAPIWarning 

224 else: 

225 warning_cls = exc.RemovedIn20Warning 

226 

227 return deprecated("2.0", message=message, warning=warning_cls, **kw) 

228 

229 

230def deprecated_params(**specs): 

231 """Decorates a function to warn on use of certain parameters. 

232 

233 e.g. :: 

234 

235 @deprecated_params( 

236 weak_identity_map=( 

237 "0.7", 

238 "the :paramref:`.Session.weak_identity_map parameter " 

239 "is deprecated." 

240 ) 

241 

242 ) 

243 

244 """ 

245 

246 messages = {} 

247 versions = {} 

248 version_warnings = {} 

249 

250 for param, (version, message) in specs.items(): 

251 versions[param] = version 

252 messages[param] = _sanitize_restructured_text(message) 

253 version_warnings[param] = ( 

254 exc.RemovedIn20Warning 

255 if version == "2.0" 

256 else exc.SADeprecationWarning 

257 ) 

258 

259 def decorate(fn): 

260 spec = compat.inspect_getfullargspec(fn) 

261 

262 if spec.defaults is not None: 

263 defaults = dict( 

264 zip( 

265 spec.args[(len(spec.args) - len(spec.defaults)) :], 

266 spec.defaults, 

267 ) 

268 ) 

269 check_defaults = set(defaults).intersection(messages) 

270 check_kw = set(messages).difference(defaults) 

271 else: 

272 check_defaults = () 

273 check_kw = set(messages) 

274 

275 check_any_kw = spec.varkw 

276 

277 @decorator 

278 def warned(fn, *args, **kwargs): 

279 for m in check_defaults: 

280 if (defaults[m] is None and kwargs[m] is not None) or ( 

281 defaults[m] is not None and kwargs[m] != defaults[m] 

282 ): 

283 _warn_with_version( 

284 messages[m], 

285 versions[m], 

286 version_warnings[m], 

287 stacklevel=3, 

288 ) 

289 

290 if check_any_kw in messages and set(kwargs).difference( 

291 check_defaults 

292 ): 

293 

294 _warn_with_version( 

295 messages[check_any_kw], 

296 versions[check_any_kw], 

297 version_warnings[check_any_kw], 

298 stacklevel=3, 

299 ) 

300 

301 for m in check_kw: 

302 if m in kwargs: 

303 _warn_with_version( 

304 messages[m], 

305 versions[m], 

306 version_warnings[m], 

307 stacklevel=3, 

308 ) 

309 return fn(*args, **kwargs) 

310 

311 doc = fn.__doc__ is not None and fn.__doc__ or "" 

312 if doc: 

313 doc = inject_param_text( 

314 doc, 

315 { 

316 param: ".. deprecated:: %s %s" 

317 % ("1.4" if version == "2.0" else version, (message or "")) 

318 for param, (version, message) in specs.items() 

319 }, 

320 ) 

321 decorated = warned(fn) 

322 decorated.__doc__ = doc 

323 return decorated 

324 

325 return decorate 

326 

327 

328def _sanitize_restructured_text(text): 

329 def repl(m): 

330 type_, name = m.group(1, 2) 

331 if type_ in ("func", "meth"): 

332 name += "()" 

333 return name 

334 

335 text = re.sub(r":ref:`(.+) <.*>`", lambda m: '"%s"' % m.group(1), text) 

336 return re.sub(r"\:(\w+)\:`~?(?:_\w+)?\.?(.+?)`", repl, text) 

337 

338 

339def _decorate_cls_with_warning( 

340 cls, constructor, wtype, message, version, docstring_header=None 

341): 

342 doc = cls.__doc__ is not None and cls.__doc__ or "" 

343 if docstring_header is not None: 

344 

345 if constructor is not None: 

346 docstring_header %= dict(func=constructor) 

347 

348 if issubclass(wtype, exc.Base20DeprecationWarning): 

349 docstring_header += ( 

350 " (Background on SQLAlchemy 2.0 at: " 

351 ":ref:`migration_20_toplevel`)" 

352 ) 

353 doc = inject_docstring_text(doc, docstring_header, 1) 

354 

355 if type(cls) is type: 

356 clsdict = dict(cls.__dict__) 

357 clsdict["__doc__"] = doc 

358 clsdict.pop("__dict__", None) 

359 clsdict.pop("__weakref__", None) 

360 cls = type(cls.__name__, cls.__bases__, clsdict) 

361 if constructor is not None: 

362 constructor_fn = clsdict[constructor] 

363 

364 else: 

365 cls.__doc__ = doc 

366 if constructor is not None: 

367 constructor_fn = getattr(cls, constructor) 

368 

369 if constructor is not None: 

370 setattr( 

371 cls, 

372 constructor, 

373 _decorate_with_warning( 

374 constructor_fn, wtype, message, version, None 

375 ), 

376 ) 

377 return cls 

378 

379 

380def _decorate_with_warning( 

381 func, wtype, message, version, docstring_header=None, enable_warnings=True 

382): 

383 """Wrap a function with a warnings.warn and augmented docstring.""" 

384 

385 message = _sanitize_restructured_text(message) 

386 

387 if issubclass(wtype, exc.Base20DeprecationWarning): 

388 doc_only = ( 

389 " (Background on SQLAlchemy 2.0 at: " 

390 ":ref:`migration_20_toplevel`)" 

391 ) 

392 else: 

393 doc_only = "" 

394 

395 @decorator 

396 def warned(fn, *args, **kwargs): 

397 skip_warning = not enable_warnings or kwargs.pop( 

398 "_sa_skip_warning", False 

399 ) 

400 if not skip_warning: 

401 _warn_with_version(message, version, wtype, stacklevel=3) 

402 return fn(*args, **kwargs) 

403 

404 doc = func.__doc__ is not None and func.__doc__ or "" 

405 if docstring_header is not None: 

406 docstring_header %= dict(func=func.__name__) 

407 

408 docstring_header += doc_only 

409 

410 doc = inject_docstring_text(doc, docstring_header, 1) 

411 

412 decorated = warned(func) 

413 decorated.__doc__ = doc 

414 decorated._sa_warn = lambda: _warn_with_version( 

415 message, version, wtype, stacklevel=3 

416 ) 

417 return decorated