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
« 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
8"""Helpers related to deprecation of functions, methods, classes, other
9functionality."""
11import os
12import re
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
23SQLALCHEMY_WARN_20 = False
25if os.getenv("SQLALCHEMY_WARN_20", "false").lower() in ("true", "yes", "1"):
26 SQLALCHEMY_WARN_20 = True
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
36 warn = type_(msg, code=code)
37 warn.deprecated_since = version
39 _warnings_warn(warn, stacklevel=stacklevel + 1)
42def warn_deprecated(msg, version, stacklevel=3, code=None):
43 _warn_with_version(
44 msg, version, exc.SADeprecationWarning, stacklevel, code=code
45 )
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.
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 )
60def warn_deprecated_20(msg, stacklevel=3, code=None):
62 _warn_with_version(
63 msg,
64 exc.RemovedIn20Warning.deprecated_since,
65 exc.RemovedIn20Warning,
66 stacklevel,
67 code=code,
68 )
71def deprecated_cls(version, message, constructor="__init__"):
72 header = ".. deprecated:: %s %s" % (version, (message or ""))
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 )
84 return decorate
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 )
101 if alternative:
102 message += " " + alternative
104 if becomes_legacy:
105 warning_cls = exc.LegacyAPIWarning
106 else:
107 warning_cls = exc.RemovedIn20Warning
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 )
119 return decorate
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.
131 :param version:
132 Issue version in the warning.
134 :param message:
135 If provided, issue message in the warning. A sensible default
136 is used if not provided.
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.
143 """
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".
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
161 if message is None:
162 message = "Call to deprecated function %(func)s"
164 if warning is None:
165 warning = exc.SADeprecationWarning
167 if warning is not exc.RemovedIn20Warning:
168 message += " (deprecated since: %s)" % version
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 )
180 return decorate
183def moved_20(message, **kw):
184 return deprecated(
185 "2.0", message=message, warning=exc.MovedIn20Warning, **kw
186 )
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 )
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 )
219 if alternative:
220 message += " " + alternative
222 if becomes_legacy:
223 warning_cls = exc.LegacyAPIWarning
224 else:
225 warning_cls = exc.RemovedIn20Warning
227 return deprecated("2.0", message=message, warning=warning_cls, **kw)
230def deprecated_params(**specs):
231 """Decorates a function to warn on use of certain parameters.
233 e.g. ::
235 @deprecated_params(
236 weak_identity_map=(
237 "0.7",
238 "the :paramref:`.Session.weak_identity_map parameter "
239 "is deprecated."
240 )
242 )
244 """
246 messages = {}
247 versions = {}
248 version_warnings = {}
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 )
259 def decorate(fn):
260 spec = compat.inspect_getfullargspec(fn)
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)
275 check_any_kw = spec.varkw
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 )
290 if check_any_kw in messages and set(kwargs).difference(
291 check_defaults
292 ):
294 _warn_with_version(
295 messages[check_any_kw],
296 versions[check_any_kw],
297 version_warnings[check_any_kw],
298 stacklevel=3,
299 )
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)
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
325 return decorate
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
335 text = re.sub(r":ref:`(.+) <.*>`", lambda m: '"%s"' % m.group(1), text)
336 return re.sub(r"\:(\w+)\:`~?(?:_\w+)?\.?(.+?)`", repl, text)
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:
345 if constructor is not None:
346 docstring_header %= dict(func=constructor)
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)
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]
364 else:
365 cls.__doc__ = doc
366 if constructor is not None:
367 constructor_fn = getattr(cls, constructor)
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
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."""
385 message = _sanitize_restructured_text(message)
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 = ""
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)
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__)
408 docstring_header += doc_only
410 doc = inject_docstring_text(doc, docstring_header, 1)
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