diff options
author | Biswakalyan Bhuyan <biswa@surgot.in> | 2022-11-13 23:46:45 +0530 |
---|---|---|
committer | Biswakalyan Bhuyan <biswa@surgot.in> | 2022-11-13 23:46:45 +0530 |
commit | 9468226a9e2e2ab8cdd599f1d8538e860ca86120 (patch) | |
tree | 0a77ada226d6db80639f96b438bf83e4e756edb5 /env/lib/python3.10/site-packages/deprecation.py | |
download | idcard-9468226a9e2e2ab8cdd599f1d8538e860ca86120.tar.gz idcard-9468226a9e2e2ab8cdd599f1d8538e860ca86120.tar.bz2 idcard-9468226a9e2e2ab8cdd599f1d8538e860ca86120.zip |
id card generator
Diffstat (limited to 'env/lib/python3.10/site-packages/deprecation.py')
-rw-r--r-- | env/lib/python3.10/site-packages/deprecation.py | 290 |
1 files changed, 290 insertions, 0 deletions
diff --git a/env/lib/python3.10/site-packages/deprecation.py b/env/lib/python3.10/site-packages/deprecation.py new file mode 100644 index 0000000..0217b58 --- /dev/null +++ b/env/lib/python3.10/site-packages/deprecation.py @@ -0,0 +1,290 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +import collections +import functools +import textwrap +import warnings + +from packaging import version +from datetime import date + +__version__ = "2.1.0" + +# This is mostly here so automodule docs are ordered more ideally. +__all__ = ["deprecated", "message_location", "fail_if_not_removed", + "DeprecatedWarning", "UnsupportedWarning"] + +#: Location where the details are added to a deprecated docstring +#: +#: When set to ``"bottom"``, the details are appended to the end. +#: When set to ``"top"``, the details are inserted between the +#: summary line and docstring contents. +message_location = "bottom" + + +class DeprecatedWarning(DeprecationWarning): + """A warning class for deprecated methods + + This is a specialization of the built-in :class:`DeprecationWarning`, + adding parameters that allow us to get information into the __str__ + that ends up being sent through the :mod:`warnings` system. + The attributes aren't able to be retrieved after the warning gets + raised and passed through the system as only the class--not the + instance--and message are what gets preserved. + + :param function: The function being deprecated. + :param deprecated_in: The version that ``function`` is deprecated in + :param removed_in: The version or :class:`datetime.date` specifying + when ``function`` gets removed. + :param details: Optional details about the deprecation. Most often + this will include directions on what to use instead + of the now deprecated code. + """ + + def __init__(self, function, deprecated_in, removed_in, details=""): + # NOTE: The docstring only works for this class if it appears up + # near the class name, not here inside __init__. I think it has + # to do with being an exception class. + self.function = function + self.deprecated_in = deprecated_in + self.removed_in = removed_in + self.details = details + super(DeprecatedWarning, self).__init__(function, deprecated_in, + removed_in, details) + + def __str__(self): + # Use a defaultdict to give us the empty string + # when a part isn't included. + parts = collections.defaultdict(str) + parts["function"] = self.function + + if self.deprecated_in: + parts["deprecated"] = " as of %s" % self.deprecated_in + if self.removed_in: + parts["removed"] = " and will be removed {} {}".format("on" if isinstance(self.removed_in, date) else "in", + self.removed_in) + if any([self.deprecated_in, self.removed_in, self.details]): + parts["period"] = "." + if self.details: + parts["details"] = " %s" % self.details + + return ("%(function)s is deprecated%(deprecated)s%(removed)s" + "%(period)s%(details)s" % (parts)) + + +class UnsupportedWarning(DeprecatedWarning): + """A warning class for methods to be removed + + This is a subclass of :class:`~deprecation.DeprecatedWarning` and is used + to output a proper message about a function being unsupported. + Additionally, the :func:`~deprecation.fail_if_not_removed` decorator + will handle this warning and cause any tests to fail if the system + under test uses code that raises this warning. + """ + + def __str__(self): + parts = collections.defaultdict(str) + parts["function"] = self.function + parts["removed"] = self.removed_in + + if self.details: + parts["details"] = " %s" % self.details + + return ("%(function)s is unsupported as of %(removed)s." + "%(details)s" % (parts)) + + +def deprecated(deprecated_in=None, removed_in=None, current_version=None, + details=""): + """Decorate a function to signify its deprecation + + This function wraps a method that will soon be removed and does two things: + * The docstring of the method will be modified to include a notice + about deprecation, e.g., "Deprecated since 0.9.11. Use foo instead." + * Raises a :class:`~deprecation.DeprecatedWarning` + via the :mod:`warnings` module, which is a subclass of the built-in + :class:`DeprecationWarning`. Note that built-in + :class:`DeprecationWarning`s are ignored by default, so for users + to be informed of said warnings they will need to enable them--see + the :mod:`warnings` module documentation for more details. + + :param deprecated_in: The version at which the decorated method is + considered deprecated. This will usually be the + next version to be released when the decorator is + added. The default is **None**, which effectively + means immediate deprecation. If this is not + specified, then the `removed_in` and + `current_version` arguments are ignored. + :param removed_in: The version or :class:`datetime.date` when the decorated + method will be removed. The default is **None**, + specifying that the function is not currently planned + to be removed. + Note: This parameter cannot be set to a value if + `deprecated_in=None`. + :param current_version: The source of version information for the + currently running code. This will usually be + a `__version__` attribute on your library. + The default is `None`. + When `current_version=None` the automation to + determine if the wrapped function is actually + in a period of deprecation or time for removal + does not work, causing a + :class:`~deprecation.DeprecatedWarning` + to be raised in all cases. + :param details: Extra details to be added to the method docstring and + warning. For example, the details may point users to + a replacement method, such as "Use the foo_bar + method instead". By default there are no details. + """ + # You can't just jump to removal. It's weird, unfair, and also makes + # building up the docstring weird. + if deprecated_in is None and removed_in is not None: + raise TypeError("Cannot set removed_in to a value " + "without also setting deprecated_in") + + # Only warn when it's appropriate. There may be cases when it makes sense + # to add this decorator before a formal deprecation period begins. + # In CPython, PendingDeprecatedWarning gets used in that period, + # so perhaps mimick that at some point. + is_deprecated = False + is_unsupported = False + + # StrictVersion won't take a None or a "", so make whatever goes to it + # is at least *something*. Compare versions only if removed_in is not + # of type datetime.date + if isinstance(removed_in, date): + if date.today() >= removed_in: + is_unsupported = True + else: + is_deprecated = True + elif current_version: + current_version = version.parse(current_version) + + if (removed_in + and current_version >= version.parse(removed_in)): + is_unsupported = True + elif (deprecated_in + and current_version >= version.parse(deprecated_in)): + is_deprecated = True + else: + # If we can't actually calculate that we're in a period of + # deprecation...well, they used the decorator, so it's deprecated. + # This will cover the case of someone just using + # @deprecated("1.0") without the other advantages. + is_deprecated = True + + should_warn = any([is_deprecated, is_unsupported]) + + def _function_wrapper(function): + if should_warn: + # Everything *should* have a docstring, but just in case... + existing_docstring = function.__doc__ or "" + + # The various parts of this decorator being optional makes for + # a number of ways the deprecation notice could go. The following + # makes for a nicely constructed sentence with or without any + # of the parts. + + # If removed_in is a date, use "removed on" + # If removed_in is a version, use "removed in" + parts = { + "deprecated_in": + " %s" % deprecated_in if deprecated_in else "", + "removed_in": + "\n This will be removed {} {}.".format("on" if isinstance(removed_in, date) else "in", + removed_in) if removed_in else "", + "details": + " %s" % details if details else ""} + + deprecation_note = (".. deprecated::{deprecated_in}" + "{removed_in}{details}".format(**parts)) + + # default location for insertion of deprecation note + loc = 1 + + # split docstring at first occurrence of newline + string_list = existing_docstring.split("\n", 1) + + if len(string_list) > 1: + # With a multi-line docstring, when we modify + # existing_docstring to add our deprecation_note, + # if we're not careful we'll interfere with the + # indentation levels of the contents below the + # first line, or as PEP 257 calls it, the summary + # line. Since the summary line can start on the + # same line as the """, dedenting the whole thing + # won't help. Split the summary and contents up, + # dedent the contents independently, then join + # summary, dedent'ed contents, and our + # deprecation_note. + + # in-place dedent docstring content + string_list[1] = textwrap.dedent(string_list[1]) + + # we need another newline + string_list.insert(loc, "\n") + + # change the message_location if we add to end of docstring + # do this always if not "top" + if message_location != "top": + loc = 3 + + # insert deprecation note and dual newline + string_list.insert(loc, deprecation_note) + string_list.insert(loc, "\n\n") + + function.__doc__ = "".join(string_list) + + @functools.wraps(function) + def _inner(*args, **kwargs): + if should_warn: + if is_unsupported: + cls = UnsupportedWarning + else: + cls = DeprecatedWarning + + the_warning = cls(function.__name__, deprecated_in, + removed_in, details) + warnings.warn(the_warning, category=DeprecationWarning, + stacklevel=2) + + return function(*args, **kwargs) + return _inner + return _function_wrapper + + +def fail_if_not_removed(method): + """Decorate a test method to track removal of deprecated code + + This decorator catches :class:`~deprecation.UnsupportedWarning` + warnings that occur during testing and causes unittests to fail, + making it easier to keep track of when code should be removed. + + :raises: :class:`AssertionError` if an + :class:`~deprecation.UnsupportedWarning` + is raised while running the test method. + """ + # NOTE(briancurtin): Unless this is named test_inner, nose won't work + # properly. See Issue #32. + @functools.wraps(method) + def test_inner(*args, **kwargs): + with warnings.catch_warnings(record=True) as caught_warnings: + warnings.simplefilter("always") + rv = method(*args, **kwargs) + + for warning in caught_warnings: + if warning.category == UnsupportedWarning: + raise AssertionError( + ("%s uses a function that should be removed: %s" % + (method, str(warning.message)))) + return rv + return test_inner |