pax_global_header00006660000000000000000000000064140740206310014507gustar00rootroot0000000000000052 comment=0bf5bf5f2a4775d6cbd634594757dac3524e5f23 debtcollector-2.3.0/000077500000000000000000000000001407402063100143365ustar00rootroot00000000000000debtcollector-2.3.0/.coveragerc000066400000000000000000000002011407402063100164500ustar00rootroot00000000000000[run] branch = True source = debtcollector omit = debtcollector/tests/*,debtcollector/openstack/* [report] ignore_errors = True debtcollector-2.3.0/.gitignore000066400000000000000000000010651407402063100163300ustar00rootroot00000000000000*.py[cod] # C extensions *.so # Packages *.egg *.egg-info dist build eggs parts bin var sdist develop-eggs .installed.cfg lib lib64 # Installer logs pip-log.txt # Unit test / coverage reports .coverage .tox .stestr/ .venv # Translations *.mo # Mr Developer .mr.developer.cfg .project .pydevproject # Complexity output/*.html output/*/index.html # Sphinx doc/build # pbr generates these AUTHORS ChangeLog # Editors *~ .*.swp .*sw? # IPython Files .ipynb_checkpoints/ *.ipynb # reno build releasenotes/build RELEASENOTES.rst releasenotes/notes/reno.cache debtcollector-2.3.0/.gitreview000066400000000000000000000001201407402063100163350ustar00rootroot00000000000000[gerrit] host=review.opendev.org port=29418 project=openstack/debtcollector.git debtcollector-2.3.0/.mailmap000066400000000000000000000001301407402063100157510ustar00rootroot00000000000000# Format is: # # debtcollector-2.3.0/.pre-commit-config.yaml000066400000000000000000000025231407402063100206210ustar00rootroot00000000000000# We from the Oslo project decided to pin repos based on the # commit hash instead of the version tag to prevend arbitrary # code from running in developer's machines. To update to a # newer version, run `pre-commit autoupdate` and then replace # the newer versions with their commit hash. default_language_version: python: python3 repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: 9136088a246768144165fcc3ecc3d31bb686920a # v3.3.0 hooks: - id: trailing-whitespace # Replaces or checks mixed line ending - id: mixed-line-ending args: ['--fix', 'lf'] exclude: '.*\.(svg)$' # Forbid files which have a UTF-8 byte-order marker - id: check-byte-order-marker # Checks that non-binary executables have a proper shebang - id: check-executables-have-shebangs # Check for files that contain merge conflict strings. - id: check-merge-conflict # Check for debugger imports and py37+ breakpoint() # calls in python source - id: debug-statements - id: check-yaml files: .*\.(yaml|yml)$ - repo: local hooks: - id: flake8 name: flake8 additional_dependencies: - hacking>=3.0.1,<3.1.0 language: python entry: flake8 files: '^.*\.py$' exclude: '^(doc|releasenotes|tools)/.*$' debtcollector-2.3.0/.stestr.conf000066400000000000000000000000651407402063100166100ustar00rootroot00000000000000[DEFAULT] test_path=./debtcollector/tests top_dir=./ debtcollector-2.3.0/.zuul.yaml000066400000000000000000000003431407402063100162770ustar00rootroot00000000000000- project: templates: - check-requirements - lib-forward-testing-python3 - openstack-python3-wallaby-jobs - periodic-stable-jobs - publish-openstack-docs-pti - release-notes-jobs-python3 debtcollector-2.3.0/CONTRIBUTING.rst000066400000000000000000000010261407402063100167760ustar00rootroot00000000000000If you would like to contribute to the development of OpenStack, you must follow the steps in this page: https://docs.openstack.org/infra/manual/developers.html Once those steps have been completed, changes to OpenStack should be submitted for review via the Gerrit tool, following the workflow documented at: https://docs.openstack.org/infra/manual/developers.html#development-workflow Pull requests submitted through GitHub will be ignored. Bugs should be filed on Launchpad, not GitHub: https://bugs.launchpad.net/debtcollector debtcollector-2.3.0/HACKING.rst000066400000000000000000000002231407402063100161310ustar00rootroot00000000000000debtcollector Style Commandments ================================ Read the OpenStack Style Commandments https://docs.openstack.org/hacking/latest debtcollector-2.3.0/LICENSE000066400000000000000000000236361407402063100153550ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. debtcollector-2.3.0/README.rst000066400000000000000000000023431407402063100160270ustar00rootroot00000000000000======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/debtcollector.svg :target: https://governance.openstack.org/tc/reference/tags/index.html .. Change things from this point on Debtcollector ============= .. image:: https://img.shields.io/pypi/v/debtcollector.svg :target: https://pypi.org/project/debtcollector/ :alt: Latest Version A collection of Python deprecation patterns and strategies that help you collect your technical debt in a non-destructive manner. The goal of this library is to provide well documented developer facing deprecation patterns that start of with a basic set and can expand into a larger set of patterns as time goes on. The desired output of these patterns is to apply the warnings module to emit DeprecationWarning or PendingDeprecationWarning or similar derivative to developers using libraries (or potentially applications) about future deprecations. * Free software: Apache license * Documentation: https://docs.openstack.org/debtcollector/latest * Source: https://opendev.org/openstack/debtcollector * Bugs: https://bugs.launchpad.net/debtcollector * Release Notes: https://docs.openstack.org/releasenotes/debtcollector debtcollector-2.3.0/babel.cfg000066400000000000000000000000201407402063100160540ustar00rootroot00000000000000[python: **.py] debtcollector-2.3.0/debtcollector/000077500000000000000000000000001407402063100171635ustar00rootroot00000000000000debtcollector-2.3.0/debtcollector/__init__.py000066400000000000000000000044551407402063100213040ustar00rootroot00000000000000# -*- coding: utf-8 -*- # 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. try: # For Python 3.8 and later import importlib.metadata as importlib_metadata except ImportError: # For everyone else import importlib_metadata from debtcollector import _utils __version__ = importlib_metadata.version('debtcollector') def deprecate(prefix, postfix=None, message=None, version=None, removal_version=None, stacklevel=3, category=DeprecationWarning): """Helper to deprecate some thing using generated message format. :param prefix: prefix string used as the prefix of the output message :param postfix: postfix string used as the postfix of the output message :param message: message string used as ending contents of the deprecate message :param version: version string (represents the version this deprecation was created in) :param removal_version: version string (represents the version this deprecation will be removed in); a string of '?' will denote this will be removed in some future unknown version :param stacklevel: stacklevel used in the :func:`warnings.warn` function to locate where the users code is in the :func:`warnings.warn` call :param category: the :mod:`warnings` category to use, defaults to :py:class:`DeprecationWarning` if not provided """ out_message = _utils.generate_message(prefix, postfix=postfix, version=version, message=message, removal_version=removal_version) _utils.deprecation(out_message, stacklevel=stacklevel, category=category) debtcollector-2.3.0/debtcollector/_utils.py000066400000000000000000000143121407402063100210350ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2015 Yahoo! Inc. All Rights Reserved. # # 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 functools import inspect import types import warnings import six try: _TYPE_TYPE = types.TypeType except AttributeError: _TYPE_TYPE = type # See: https://docs.python.org/2/library/__builtin__.html#module-__builtin__ # and see https://docs.python.org/2/reference/executionmodel.html (and likely # others)... _BUILTIN_MODULES = ('builtins', '__builtin__', '__builtins__', 'exceptions') _enabled = True def deprecation(message, stacklevel=None, category=None): """Warns about some type of deprecation that has been (or will be) made. This helper function makes it easier to interact with the warnings module by standardizing the arguments that the warning function receives so that it is easier to use. This should be used to emit warnings to users (users can easily turn these warnings off/on, see https://docs.python.org/2/library/warnings.html as they see fit so that the messages do not fill up the users logs with warnings that they do not wish to see in production) about functions, methods, attributes or other code that is deprecated and will be removed in a future release (this is done using these warnings to avoid breaking existing users of those functions, methods, code; which a library should avoid doing by always giving at *least* N + 1 release for users to address the deprecation warnings). """ if not _enabled: return if category is None: category = DeprecationWarning if stacklevel is None: warnings.warn(message, category=category) else: warnings.warn(message, category=category, stacklevel=stacklevel) def get_qualified_name(obj): # Prefer the py3.x name (if we can get at it...) try: return (True, obj.__qualname__) except AttributeError: return (False, obj.__name__) def generate_message(prefix, postfix=None, message=None, version=None, removal_version=None): """Helper to generate a common message 'style' for deprecation helpers.""" message_components = [prefix] if version: message_components.append(" in version '%s'" % version) if removal_version: if removal_version == "?": message_components.append(" and will be removed in a future" " version") else: message_components.append(" and will be removed in version '%s'" % removal_version) if postfix: message_components.append(postfix) if message: message_components.append(": %s" % message) return ''.join(message_components) def get_assigned(decorator): """Helper to fix/workaround https://bugs.python.org/issue3445""" if six.PY3: return functools.WRAPPER_ASSIGNMENTS else: assigned = [] for attr_name in functools.WRAPPER_ASSIGNMENTS: if hasattr(decorator, attr_name): assigned.append(attr_name) return tuple(assigned) def get_class_name(obj, fully_qualified=True): """Get class name for object. If object is a type, fully qualified name of the type is returned. Else, fully qualified name of the type of the object is returned. For builtin types, just name is returned. """ if not isinstance(obj, six.class_types): obj = type(obj) try: built_in = obj.__module__ in _BUILTIN_MODULES except AttributeError: pass else: if built_in: return obj.__name__ if fully_qualified and hasattr(obj, '__module__'): return '%s.%s' % (obj.__module__, obj.__name__) else: return obj.__name__ def get_method_self(method): """Gets the ``self`` object attached to this method (or none).""" if not inspect.ismethod(method): return None try: return six.get_method_self(method) except AttributeError: return None def get_callable_name(function): """Generate a name from callable. Tries to do the best to guess fully qualified callable name. """ method_self = get_method_self(function) if method_self is not None: # This is a bound method. if isinstance(method_self, six.class_types): # This is a bound class method. im_class = method_self else: im_class = type(method_self) try: parts = (im_class.__module__, function.__qualname__) except AttributeError: parts = (im_class.__module__, im_class.__name__, function.__name__) elif inspect.ismethod(function) or inspect.isfunction(function): # This could be a function, a static method, a unbound method... try: parts = (function.__module__, function.__qualname__) except AttributeError: if hasattr(function, 'im_class'): # This is a unbound method, which exists only in python 2.x im_class = function.im_class parts = (im_class.__module__, im_class.__name__, function.__name__) else: parts = (function.__module__, function.__name__) else: im_class = type(function) if im_class is _TYPE_TYPE: im_class = function try: parts = (im_class.__module__, im_class.__qualname__) except AttributeError: parts = (im_class.__module__, im_class.__name__) # When running under sphinx it appears this can be none? if so just # don't include it... mod, rest = (parts[0], parts[1:]) if not mod: return '.'.join(rest) else: return '.'.join(parts) debtcollector-2.3.0/debtcollector/fixtures/000077500000000000000000000000001407402063100210345ustar00rootroot00000000000000debtcollector-2.3.0/debtcollector/fixtures/__init__.py000066400000000000000000000000001407402063100231330ustar00rootroot00000000000000debtcollector-2.3.0/debtcollector/fixtures/disable.py000066400000000000000000000022421407402063100230110ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2015 Yahoo! Inc. All Rights Reserved. # # 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 fixtures from debtcollector import _utils class DisableFixture(fixtures.Fixture): """Fixture that disables debtcollector triggered warnings. This does **not** disable warnings calls emitted by other libraries. This can be used like:: from debtcollector.fixtures import disable with disable.DisableFixture(): """ def _setUp(self): self.addCleanup(setattr, _utils, "_enabled", True) _utils._enabled = False debtcollector-2.3.0/debtcollector/moves.py000066400000000000000000000203511407402063100206670ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2015 Yahoo! Inc. All Rights Reserved. # # 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 inspect import six import wrapt from debtcollector import _utils _KIND_MOVED_PREFIX_TPL = "%s '%s' has moved to '%s'" _CLASS_MOVED_PREFIX_TPL = "Class '%s' has moved to '%s'" _MOVED_CALLABLE_POSTFIX = "()" _FUNC_MOVED_PREFIX_TPL = "Function '%s' has moved to '%s'" def _moved_decorator(kind, new_attribute_name, message=None, version=None, removal_version=None, stacklevel=3, attr_postfix=None, category=None): """Decorates a method/property that was moved to another location.""" def decorator(f): fully_qualified, old_attribute_name = _utils.get_qualified_name(f) if attr_postfix: old_attribute_name += attr_postfix @wrapt.decorator def wrapper(wrapped, instance, args, kwargs): base_name = _utils.get_class_name(wrapped, fully_qualified=False) if fully_qualified: old_name = old_attribute_name else: old_name = ".".join((base_name, old_attribute_name)) new_name = ".".join((base_name, new_attribute_name)) prefix = _KIND_MOVED_PREFIX_TPL % (kind, old_name, new_name) out_message = _utils.generate_message( prefix, message=message, version=version, removal_version=removal_version) _utils.deprecation(out_message, stacklevel=stacklevel, category=category) return wrapped(*args, **kwargs) return wrapper(f) return decorator def moved_function(new_func, old_func_name, old_module_name, message=None, version=None, removal_version=None, stacklevel=3, category=None): """Deprecates a function that was moved to another location. This generates a wrapper around ``new_func`` that will emit a deprecation warning when called. The warning message will include the new location to obtain the function from. """ new_func_full_name = _utils.get_callable_name(new_func) new_func_full_name += _MOVED_CALLABLE_POSTFIX old_func_full_name = ".".join([old_module_name, old_func_name]) old_func_full_name += _MOVED_CALLABLE_POSTFIX prefix = _FUNC_MOVED_PREFIX_TPL % (old_func_full_name, new_func_full_name) out_message = _utils.generate_message(prefix, message=message, version=version, removal_version=removal_version) @six.wraps(new_func, assigned=_utils.get_assigned(new_func)) def old_new_func(*args, **kwargs): _utils.deprecation(out_message, stacklevel=stacklevel, category=category) return new_func(*args, **kwargs) old_new_func.__name__ = old_func_name old_new_func.__module__ = old_module_name return old_new_func class moved_read_only_property(object): """Descriptor for read-only properties moved to another location. This works like the ``@property`` descriptor but can be used instead to provide the same functionality and also interact with the :mod:`warnings` module to warn when a property is accessed, so that users of those properties can know that a previously read-only property at a prior location/name has moved to another location/name. :param old_name: old attribute location/name :param new_name: new attribute location/name :param version: version string (represents the version this deprecation was created in) :param removal_version: version string (represents the version this deprecation will be removed in); a string of '?' will denote this will be removed in some future unknown version :param stacklevel: stacklevel used in the :func:`warnings.warn` function to locate where the users code is when reporting the deprecation call (the default being 3) :param category: the :mod:`warnings` category to use, defaults to :py:class:`DeprecationWarning` if not provided """ def __init__(self, old_name, new_name, version=None, removal_version=None, stacklevel=3, category=None): self._old_name = old_name self._new_name = new_name self._message = _utils.generate_message( "Read-only property '%s' has moved" " to '%s'" % (self._old_name, self._new_name), version=version, removal_version=removal_version) self._stacklevel = stacklevel self._category = category def __get__(self, instance, owner): _utils.deprecation(self._message, stacklevel=self._stacklevel, category=self._category) # This handles the descriptor being applied on a # instance or a class and makes both work correctly... if instance is not None: real_owner = instance else: real_owner = owner return getattr(real_owner, self._new_name) def moved_method(new_method_name, message=None, version=None, removal_version=None, stacklevel=3, category=None): """Decorates an *instance* method that was moved to another location.""" if not new_method_name.endswith(_MOVED_CALLABLE_POSTFIX): new_method_name += _MOVED_CALLABLE_POSTFIX return _moved_decorator('Method', new_method_name, message=message, version=version, removal_version=removal_version, stacklevel=stacklevel, attr_postfix=_MOVED_CALLABLE_POSTFIX, category=category) def moved_property(new_attribute_name, message=None, version=None, removal_version=None, stacklevel=3, category=None): """Decorates an *instance* property that was moved to another location.""" return _moved_decorator('Property', new_attribute_name, message=message, version=version, removal_version=removal_version, stacklevel=stacklevel, category=category) def moved_class(new_class, old_class_name, old_module_name, message=None, version=None, removal_version=None, stacklevel=3, category=None): """Deprecates a class that was moved to another location. This creates a 'new-old' type that can be used for a deprecation period that can be inherited from. This will emit warnings when the old locations class is initialized, telling where the new and improved location for the old class now is. """ if not inspect.isclass(new_class): _qual, type_name = _utils.get_qualified_name(type(new_class)) raise TypeError("Unexpected class type '%s' (expected" " class type only)" % type_name) old_name = ".".join((old_module_name, old_class_name)) new_name = _utils.get_class_name(new_class) prefix = _CLASS_MOVED_PREFIX_TPL % (old_name, new_name) out_message = _utils.generate_message( prefix, message=message, version=version, removal_version=removal_version) def decorator(f): @six.wraps(f, assigned=_utils.get_assigned(f)) def wrapper(self, *args, **kwargs): _utils.deprecation(out_message, stacklevel=stacklevel, category=category) return f(self, *args, **kwargs) return wrapper old_class = type(old_class_name, (new_class,), {}) old_class.__module__ = old_module_name old_class.__init__ = decorator(old_class.__init__) return old_class debtcollector-2.3.0/debtcollector/removals.py000066400000000000000000000331061407402063100213700ustar00rootroot00000000000000# Copyright 2014 Hewlett-Packard Development Company, L.P. # # 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 functools import inspect import six import wrapt from debtcollector import _utils def _get_qualified_name(obj): return _utils.get_qualified_name(obj)[1] def _fetch_first_result(fget, fset, fdel, apply_func, value_not_found=None): """Fetch first non-none/empty result of applying ``apply_func``.""" for f in filter(None, (fget, fset, fdel)): result = apply_func(f) if result: return result return value_not_found class removed_property(object): """Property descriptor that deprecates a property. This works like the ``@property`` descriptor but can be used instead to provide the same functionality and also interact with the :mod:`warnings` module to warn when a property is accessed, set and/or deleted. :param message: string used as ending contents of the deprecate message :param version: version string (represents the version this deprecation was created in) :param removal_version: version string (represents the version this deprecation will be removed in); a string of '?' will denote this will be removed in some future unknown version :param stacklevel: stacklevel used in the :func:`warnings.warn` function to locate where the users code is when reporting the deprecation call (the default being 3) :param category: the :mod:`warnings` category to use, defaults to :py:class:`DeprecationWarning` if not provided """ # Message templates that will be turned into real messages as needed. _PROPERTY_GONE_TPLS = { 'set': "Setting the '%s' property is deprecated", 'get': "Reading the '%s' property is deprecated", 'delete': "Deleting the '%s' property is deprecated", } def __init__(self, fget=None, fset=None, fdel=None, doc=None, stacklevel=3, category=DeprecationWarning, version=None, removal_version=None, message=None): self.fset = fset self.fget = fget self.fdel = fdel self.stacklevel = stacklevel self.category = category self.version = version self.removal_version = removal_version self.message = message if doc is None and inspect.isfunction(fget): doc = getattr(fget, '__doc__', None) self._message_cache = {} self.__doc__ = doc def _fetch_message_from_cache(self, kind): try: out_message = self._message_cache[kind] except KeyError: prefix_tpl = self._PROPERTY_GONE_TPLS[kind] prefix = prefix_tpl % _fetch_first_result( self.fget, self.fset, self.fdel, _get_qualified_name, value_not_found="???") out_message = _utils.generate_message( prefix, message=self.message, version=self.version, removal_version=self.removal_version) self._message_cache[kind] = out_message return out_message def __call__(self, fget, **kwargs): self.fget = fget self.message = kwargs.get('message', self.message) self.version = kwargs.get('version', self.version) self.removal_version = kwargs.get('removal_version', self.removal_version) self.stacklevel = kwargs.get('stacklevel', self.stacklevel) self.category = kwargs.get('category', self.category) self.__doc__ = kwargs.get('doc', getattr(fget, '__doc__', self.__doc__)) # Regenerate all the messages... self._message_cache.clear() return self def __delete__(self, obj): if self.fdel is None: raise AttributeError("can't delete attribute") out_message = self._fetch_message_from_cache('delete') _utils.deprecation(out_message, stacklevel=self.stacklevel, category=self.category) self.fdel(obj) def __set__(self, obj, value): if self.fset is None: raise AttributeError("can't set attribute") out_message = self._fetch_message_from_cache('set') _utils.deprecation(out_message, stacklevel=self.stacklevel, category=self.category) self.fset(obj, value) def __get__(self, obj, value): if obj is None: return self if self.fget is None: raise AttributeError("unreadable attribute") out_message = self._fetch_message_from_cache('get') _utils.deprecation(out_message, stacklevel=self.stacklevel, category=self.category) return self.fget(obj) def getter(self, fget): o = type(self)(fget, self.fset, self.fdel, self.__doc__) o.message = self.message o.version = self.version o.stacklevel = self.stacklevel o.removal_version = self.removal_version o.category = self.category return o def setter(self, fset): o = type(self)(self.fget, fset, self.fdel, self.__doc__) o.message = self.message o.version = self.version o.stacklevel = self.stacklevel o.removal_version = self.removal_version o.category = self.category return o def deleter(self, fdel): o = type(self)(self.fget, self.fset, fdel, self.__doc__) o.message = self.message o.version = self.version o.stacklevel = self.stacklevel o.removal_version = self.removal_version o.category = self.category return o def remove(f=None, message=None, version=None, removal_version=None, stacklevel=3, category=None): """Decorates a function, method, or class to emit a deprecation warning Due to limitations of the wrapt library (and python) itself, if this is applied to subclasses of metaclasses then it likely will not work as expected. More information can be found at bug #1520397 to see if this situation affects your usage of this *universal* decorator, for this specific scenario please use :py:func:`.removed_class` instead. :param str message: A message to include in the deprecation warning :param str version: Specify what version the removed function is present in :param str removal_version: What version the function will be removed. If '?' is used this implies an undefined future version :param int stacklevel: How many entries deep in the call stack before ignoring :param type category: warnings message category (this defaults to ``DeprecationWarning`` when none is provided) """ if f is None: return functools.partial(remove, message=message, version=version, removal_version=removal_version, stacklevel=stacklevel, category=category) @wrapt.decorator def wrapper(f, instance, args, kwargs): qualified, f_name = _utils.get_qualified_name(f) if qualified: if inspect.isclass(f): prefix_pre = "Using class" thing_post = '' else: prefix_pre = "Using function/method" thing_post = '()' if not qualified: prefix_pre = "Using function/method" base_name = None if instance is None: # Decorator was used on a class if inspect.isclass(f): prefix_pre = "Using class" thing_post = '' module_name = _get_qualified_name(inspect.getmodule(f)) if module_name == '__main__': f_name = _utils.get_class_name( f, fully_qualified=False) else: f_name = _utils.get_class_name( f, fully_qualified=True) # Decorator was a used on a function else: thing_post = '()' module_name = _get_qualified_name(inspect.getmodule(f)) if module_name != '__main__': f_name = _utils.get_callable_name(f) # Decorator was used on a classmethod or instancemethod else: thing_post = '()' base_name = _utils.get_class_name(instance, fully_qualified=False) if base_name: thing_name = ".".join([base_name, f_name]) else: thing_name = f_name else: thing_name = f_name if thing_post: thing_name += thing_post prefix = prefix_pre + " '%s' is deprecated" % (thing_name) out_message = _utils.generate_message( prefix, version=version, removal_version=removal_version, message=message) _utils.deprecation(out_message, stacklevel=stacklevel, category=category) return f(*args, **kwargs) return wrapper(f) def removed_kwarg(old_name, message=None, version=None, removal_version=None, stacklevel=3, category=None): """Decorates a kwarg accepting function to deprecate a removed kwarg.""" prefix = "Using the '%s' argument is deprecated" % old_name out_message = _utils.generate_message( prefix, postfix=None, message=message, version=version, removal_version=removal_version) @wrapt.decorator def wrapper(f, instance, args, kwargs): if old_name in kwargs: _utils.deprecation(out_message, stacklevel=stacklevel, category=category) return f(*args, **kwargs) return wrapper def removed_class(cls_name, replacement=None, message=None, version=None, removal_version=None, stacklevel=3, category=None): """Decorates a class to denote that it will be removed at some point.""" def _wrap_it(old_init, out_message): @six.wraps(old_init, assigned=_utils.get_assigned(old_init)) def new_init(self, *args, **kwargs): _utils.deprecation(out_message, stacklevel=stacklevel, category=category) return old_init(self, *args, **kwargs) return new_init def _check_it(cls): if not inspect.isclass(cls): _qual, type_name = _utils.get_qualified_name(type(cls)) raise TypeError("Unexpected class type '%s' (expected" " class type only)" % type_name) def _cls_decorator(cls): _check_it(cls) out_message = _utils.generate_message( "Using class '%s' (either directly or via inheritance)" " is deprecated" % cls_name, postfix=None, message=message, version=version, removal_version=removal_version) cls.__init__ = _wrap_it(cls.__init__, out_message) return cls return _cls_decorator def removed_module(module, replacement=None, message=None, version=None, removal_version=None, stacklevel=3, category=None): """Helper to be called inside a module to emit a deprecation warning :param str replacment: A location (or information about) of any potential replacement for the removed module (if applicable) :param str message: A message to include in the deprecation warning :param str version: Specify what version the removed module is present in :param str removal_version: What version the module will be removed. If '?' is used this implies an undefined future version :param int stacklevel: How many entries deep in the call stack before ignoring :param type category: warnings message category (this defaults to ``DeprecationWarning`` when none is provided) """ if inspect.ismodule(module): module_name = _get_qualified_name(module) elif isinstance(module, six.string_types): module_name = module else: _qual, type_name = _utils.get_qualified_name(type(module)) raise TypeError("Unexpected module type '%s' (expected string or" " module type only)" % type_name) prefix = "The '%s' module usage is deprecated" % module_name if replacement: postfix = ", please use %s instead" % replacement else: postfix = None out_message = _utils.generate_message(prefix, postfix=postfix, message=message, version=version, removal_version=removal_version) _utils.deprecation(out_message, stacklevel=stacklevel, category=category) debtcollector-2.3.0/debtcollector/renames.py000066400000000000000000000032671407402063100211770ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2015 Yahoo! Inc. All Rights Reserved. # # 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 wrapt from debtcollector import _utils _KWARG_RENAMED_POSTFIX_TPL = ", please use the '%s' argument instead" _KWARG_RENAMED_PREFIX_TPL = "Using the '%s' argument is deprecated" def renamed_kwarg(old_name, new_name, message=None, version=None, removal_version=None, stacklevel=3, category=None, replace=False): """Decorates a kwarg accepting function to deprecate a renamed kwarg.""" prefix = _KWARG_RENAMED_PREFIX_TPL % old_name postfix = _KWARG_RENAMED_POSTFIX_TPL % new_name out_message = _utils.generate_message( prefix, postfix=postfix, message=message, version=version, removal_version=removal_version) @wrapt.decorator def decorator(wrapped, instance, args, kwargs): if old_name in kwargs: _utils.deprecation(out_message, stacklevel=stacklevel, category=category) if replace: kwargs.setdefault(new_name, kwargs.pop(old_name)) return wrapped(*args, **kwargs) return decorator debtcollector-2.3.0/debtcollector/tests/000077500000000000000000000000001407402063100203255ustar00rootroot00000000000000debtcollector-2.3.0/debtcollector/tests/__init__.py000066400000000000000000000000001407402063100224240ustar00rootroot00000000000000debtcollector-2.3.0/debtcollector/tests/base.py000066400000000000000000000014201407402063100216060ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright 2010-2011 OpenStack Foundation # Copyright (c) 2013 Hewlett-Packard Development Company, L.P. # # 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 unittest class TestCase(unittest.TestCase): """Test case base class for all unit tests.""" debtcollector-2.3.0/debtcollector/tests/test_deprecation.py000066400000000000000000000524371407402063100242460ustar00rootroot00000000000000# Copyright (C) 2014 Yahoo! Inc. All Rights Reserved. # # 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 inspect import warnings import debtcollector from debtcollector.fixtures import disable from debtcollector import moves from debtcollector import removals from debtcollector import renames from debtcollector.tests import base as test_base from debtcollector import updating @renames.renamed_kwarg('blip', 'blop') def blip_blop(blip=1, blop=1): return (blip, blop) def blip_blop_unwrapped(blip=1, blop=1): return (blip, blop) @renames.renamed_kwarg('blip', 'blop', category=PendingDeprecationWarning) def blip_blop_2(blip=1, blop=1): return (blip, blop) @renames.renamed_kwarg('blip', 'blop', replace=True) def blip_blop_3(blop=1): return blop @updating.updated_kwarg_default_value('type', 'cat', 'feline') def blip_blop_blip(type='cat'): return "The %s meowed quietly" % type def blip_blop_blip_unwrapped(type='cat'): return "The %s meowed quietly" % type class WoofWoof(object): @property def bark(self): return 'woof' @property @moves.moved_property('bark') def burk(self): return self.bark @property @moves.moved_property('bark', category=PendingDeprecationWarning) def berk(self): return self.bark @removals.removed_kwarg('resp', message="Please use 'response' instead") @classmethod def factory(cls, resp=None, response=None): return 'super-duper' class KittyKat(object): @moves.moved_method('supermeow') def meow(self, volume=11): return self.supermeow(volume) @moves.moved_method('supermeow', category=PendingDeprecationWarning) def maow(self, volume=11): return self.supermeow(volume) def supermeow(self, volume=11): return 'supermeow' class Giraffe(object): color = 'orange' colour = moves.moved_read_only_property('colour', 'color') @property def height(self): return 2 heightt = moves.moved_read_only_property('heightt', 'height') class NewHotness(object): def hot(self): return 'cold' @removals.remove() def crimson_lightning(fake_input=None): return fake_input def crimson_lightning_unwrapped(fake_input=None): return fake_input @removals.remove(category=PendingDeprecationWarning) def crimson_lightning_to_remove(fake_input=None): return fake_input @removals.remove() def red_comet(): return True @removals.remove(category=PendingDeprecationWarning) def blue_comet(): return True def yellow_sun(): """Yellow.""" return True yellowish_sun = moves.moved_function(yellow_sun, 'yellowish_sun', __name__) @removals.remove() class EFSF(object): pass @removals.remove(category=PendingDeprecationWarning) class EFSF_2(object): pass @removals.removed_class("StarLord") class StarLord(object): def __init__(self): self.name = "star" class StarLordJr(StarLord): def __init__(self, name): super(StarLordJr, self).__init__() self.name = name class ThingB(object): @removals.remove() def black_tristars(self): pass @removals.removed_property def green_tristars(self): return 'green' @green_tristars.setter def green_tristars(self, value): pass @green_tristars.deleter def green_tristars(self): pass @removals.removed_property(message="stop using me") def green_blue_tristars(self): return 'green-blue' @removals.remove(category=PendingDeprecationWarning) def blue_tristars(self): pass @removals.remove() @classmethod def white_wolf(cls): pass @removals.remove(category=PendingDeprecationWarning) @classmethod def yellow_wolf(cls): pass @removals.remove() @staticmethod def blue_giant(): pass @removals.remove(category=PendingDeprecationWarning) @staticmethod def green_giant(): pass OldHotness = moves.moved_class(NewHotness, 'OldHotness', __name__) OldHotness2 = moves.moved_class(NewHotness, 'OldHotness', __name__, category=PendingDeprecationWarning) class DeprecateAnythingTest(test_base.TestCase): def test_generation(self): with warnings.catch_warnings(record=True) as capture: warnings.simplefilter("always") debtcollector.deprecate("Its broken") debtcollector.deprecate("Its really broken") self.assertEqual(2, len(capture)) class MovedInheritableClassTest(test_base.TestCase): def test_broken_type_class(self): self.assertRaises(TypeError, moves.moved_class, 'b', __name__) def test_basics(self): old = OldHotness() self.assertIsInstance(old, NewHotness) self.assertEqual('cold', old.hot()) def test_warnings_emitted_creation(self): with warnings.catch_warnings(record=True) as capture: warnings.simplefilter("always") OldHotness() self.assertEqual(1, len(capture)) w = capture[0] self.assertEqual(DeprecationWarning, w.category) def test_warnings_emitted_creation_pending(self): with warnings.catch_warnings(record=True) as capture: warnings.simplefilter("always") OldHotness2() self.assertEqual(1, len(capture)) w = capture[0] self.assertEqual(PendingDeprecationWarning, w.category) def test_existing_refer_subclass(self): class MyOldThing(OldHotness): pass with warnings.catch_warnings(record=True) as capture: warnings.simplefilter("always") MyOldThing() self.assertEqual(1, len(capture)) w = capture[0] self.assertEqual(DeprecationWarning, w.category) class MovedPropertyTest(test_base.TestCase): def test_basics(self): dog = WoofWoof() self.assertEqual('woof', dog.burk) self.assertEqual('woof', dog.bark) def test_readonly_move(self): with warnings.catch_warnings(record=True) as capture: warnings.simplefilter("always") self.assertEqual('orange', Giraffe.colour) g = Giraffe() self.assertEqual(2, g.heightt) self.assertEqual(2, len(capture)) def test_warnings_emitted(self): dog = WoofWoof() with warnings.catch_warnings(record=True) as capture: warnings.simplefilter("always") self.assertEqual('woof', dog.burk) self.assertEqual(1, len(capture)) w = capture[0] self.assertEqual(DeprecationWarning, w.category) def test_warnings_emitted_pending(self): dog = WoofWoof() with warnings.catch_warnings(record=True) as capture: warnings.simplefilter("always") self.assertEqual('woof', dog.berk) self.assertEqual(1, len(capture)) w = capture[0] self.assertEqual(PendingDeprecationWarning, w.category) def test_warnings_not_emitted(self): dog = WoofWoof() with warnings.catch_warnings(record=True) as capture: warnings.simplefilter("always") self.assertEqual('woof', dog.bark) self.assertEqual(0, len(capture)) class DisabledTest(test_base.TestCase): def test_basics(self): dog = WoofWoof() c = KittyKat() with warnings.catch_warnings(record=True) as capture: warnings.simplefilter("always") with disable.DisableFixture(): self.assertTrue(yellowish_sun()) self.assertEqual('woof', dog.berk) self.assertEqual('supermeow', c.meow()) self.assertEqual(0, len(capture)) class MovedFunctionTest(test_base.TestCase): def test_basics(self): self.assertTrue(yellowish_sun()) self.assertTrue(yellow_sun()) self.assertEqual("Yellow.", yellowish_sun.__doc__) def test_warnings_emitted(self): with warnings.catch_warnings(record=True) as capture: warnings.simplefilter("always") self.assertTrue(yellowish_sun()) self.assertEqual(1, len(capture)) w = capture[0] self.assertEqual(DeprecationWarning, w.category) class MovedMethodTest(test_base.TestCase): def test_basics(self): c = KittyKat() self.assertEqual('supermeow', c.meow()) self.assertEqual('supermeow', c.supermeow()) def test_warnings_emitted(self): c = KittyKat() with warnings.catch_warnings(record=True) as capture: warnings.simplefilter("always") self.assertEqual('supermeow', c.meow()) self.assertEqual(1, len(capture)) w = capture[0] self.assertEqual(DeprecationWarning, w.category) def test_warnings_emitted_pending(self): c = KittyKat() with warnings.catch_warnings(record=True) as capture: warnings.simplefilter("always") self.assertEqual('supermeow', c.maow()) self.assertEqual(1, len(capture)) w = capture[0] self.assertEqual(PendingDeprecationWarning, w.category) def test_warnings_not_emitted(self): c = KittyKat() with warnings.catch_warnings(record=True) as capture: warnings.simplefilter("always") self.assertEqual('supermeow', c.supermeow()) self.assertEqual(0, len(capture)) def test_keeps_argspec(self): self.assertEqual(inspect.getfullargspec(KittyKat.supermeow), inspect.getfullargspec(KittyKat.meow)) class RenamedKwargTest(test_base.TestCase): def test_basics(self): self.assertEqual((1, 1), blip_blop()) self.assertEqual((2, 1), blip_blop(blip=2)) self.assertEqual((1, 2), blip_blop(blop=2)) self.assertEqual((2, 2), blip_blop(blip=2, blop=2)) self.assertEqual(2, blip_blop_3(blip=2)) self.assertEqual(2, blip_blop_3(blop=2)) def test_warnings_emitted(self): with warnings.catch_warnings(record=True) as capture: warnings.simplefilter("always") self.assertEqual((2, 1), blip_blop(blip=2)) self.assertEqual(1, len(capture)) w = capture[0] self.assertEqual(DeprecationWarning, w.category) with warnings.catch_warnings(record=True) as capture: warnings.simplefilter("always") self.assertEqual(2, blip_blop_3(blip=2)) self.assertEqual(1, len(capture)) w = capture[0] self.assertEqual(DeprecationWarning, w.category) def test_warnings_emitted_classmethod(self): with warnings.catch_warnings(record=True) as capture: warnings.simplefilter("always") WoofWoof.factory(resp="hi") self.assertEqual(1, len(capture)) w = capture[0] self.assertEqual(DeprecationWarning, w.category) with warnings.catch_warnings(record=True) as capture: warnings.simplefilter("always") WoofWoof.factory(response="hi") self.assertEqual(0, len(capture)) def test_warnings_emitted_pending(self): with warnings.catch_warnings(record=True) as capture: warnings.simplefilter("always") self.assertEqual((2, 1), blip_blop_2(blip=2)) self.assertEqual(1, len(capture)) w = capture[0] self.assertEqual(PendingDeprecationWarning, w.category) def test_warnings_not_emitted(self): with warnings.catch_warnings(record=True) as capture: warnings.simplefilter("always") self.assertEqual((1, 2), blip_blop(blop=2)) self.assertEqual(0, len(capture)) with warnings.catch_warnings(record=True) as capture: warnings.simplefilter("always") self.assertEqual(2, blip_blop_3(blop=2)) self.assertEqual(0, len(capture)) def test_argspec(self): # The decorated function keeps its argspec. self.assertEqual(inspect.getfullargspec(blip_blop_unwrapped), inspect.getfullargspec(blip_blop)) class UpdatedArgsTest(test_base.TestCase): def test_basic(self): with warnings.catch_warnings(record=True) as capture: warnings.simplefilter("always") self.assertEqual('The cat meowed quietly', blip_blop_blip()) self.assertEqual(1, len(capture)) w = capture[0] self.assertEqual(FutureWarning, w.category) def test_kwarg_set(self): with warnings.catch_warnings(record=True) as capture: warnings.simplefilter("always") self.assertEqual( 'The kitten meowed quietly', blip_blop_blip(type='kitten')) self.assertEqual(0, len(capture)) def test_argspec_preserved(self): self.assertEqual(inspect.getfullargspec(blip_blop_blip_unwrapped), inspect.getfullargspec(blip_blop_blip)) class RemovalTests(test_base.TestCase): def test_function_args(self): self.assertEqual(666, crimson_lightning(666)) def test_function_noargs(self): self.assertTrue(red_comet()) def test_function_keeps_argspec(self): # The decorated function keeps its argspec. self.assertEqual( inspect.getfullargspec(crimson_lightning_unwrapped), inspect.getfullargspec(crimson_lightning)) def test_deprecated_kwarg(self): @removals.removed_kwarg('b') def f(b=2): return b with warnings.catch_warnings(record=True) as capture: warnings.simplefilter("always") self.assertEqual(3, f(b=3)) self.assertEqual(1, len(capture)) w = capture[0] self.assertEqual(DeprecationWarning, w.category) with warnings.catch_warnings(record=True) as capture: warnings.simplefilter("always") self.assertEqual(2, f()) self.assertEqual(0, len(capture)) def test_removed_kwarg_keeps_argspec(self): @removals.removed_kwarg('b') def f(b=2): return b def f_unwrapped(b=2): return b self.assertEqual(inspect.getfullargspec(f_unwrapped), inspect.getfullargspec(f)) def test_pending_deprecated_kwarg(self): @removals.removed_kwarg('b', category=PendingDeprecationWarning) def f(b=2): return b with warnings.catch_warnings(record=True) as capture: warnings.simplefilter("always") self.assertEqual(3, f(b=3)) self.assertEqual(1, len(capture)) w = capture[0] self.assertEqual(PendingDeprecationWarning, w.category) with warnings.catch_warnings(record=True) as capture: warnings.simplefilter("always") self.assertEqual(2, f()) self.assertEqual(0, len(capture)) def test_warnings_emitted_property(self): with warnings.catch_warnings(record=True) as capture: warnings.simplefilter("always") o = ThingB() self.assertEqual('green', o.green_tristars) o.green_tristars = 'b' del o.green_tristars self.assertEqual(3, len(capture)) w = capture[0] self.assertEqual(DeprecationWarning, w.category) def test_warnings_emitted_property_custom_message(self): with warnings.catch_warnings(record=True) as capture: warnings.simplefilter("always") o = ThingB() self.assertEqual('green-blue', o.green_blue_tristars) self.assertEqual(1, len(capture)) w = capture[0] self.assertIn('stop using me', str(w.message)) self.assertEqual(DeprecationWarning, w.category) def test_warnings_emitted_function_args(self): with warnings.catch_warnings(record=True) as capture: warnings.simplefilter("always") self.assertEqual(666, crimson_lightning(666)) self.assertEqual(1, len(capture)) w = capture[0] self.assertEqual(DeprecationWarning, w.category) def test_pending_warnings_emitted_function_args(self): with warnings.catch_warnings(record=True) as capture: warnings.simplefilter("always") self.assertEqual(666, crimson_lightning_to_remove(666)) self.assertEqual(1, len(capture)) w = capture[0] self.assertEqual(PendingDeprecationWarning, w.category) def test_warnings_emitted_function_noargs(self): with warnings.catch_warnings(record=True) as capture: warnings.simplefilter("always") self.assertTrue(red_comet()) self.assertEqual(1, len(capture)) w = capture[0] self.assertEqual(DeprecationWarning, w.category) def test_pending_warnings_emitted_function_noargs(self): with warnings.catch_warnings(record=True) as capture: warnings.simplefilter("always") self.assertTrue(blue_comet()) self.assertEqual(1, len(capture)) w = capture[0] self.assertEqual(PendingDeprecationWarning, w.category) def test_warnings_emitted_class(self): with warnings.catch_warnings(record=True) as capture: warnings.simplefilter("always") EFSF() self.assertEqual(1, len(capture)) w = capture[0] self.assertEqual(DeprecationWarning, w.category) def test_pending_warnings_emitted_class(self): with warnings.catch_warnings(record=True) as capture: warnings.simplefilter("always") EFSF_2() self.assertEqual(1, len(capture)) w = capture[0] self.assertEqual(PendingDeprecationWarning, w.category) def test_pending_warnings_emitted_class_direct(self): with warnings.catch_warnings(record=True) as capture: warnings.simplefilter("always") s = StarLord() self.assertEqual(1, len(capture)) w = capture[0] self.assertEqual(DeprecationWarning, w.category) self.assertEqual("star", s.name) def test_pending_warnings_emitted_class_inherit(self): with warnings.catch_warnings(record=True) as capture: warnings.simplefilter("always") s = StarLordJr("star_jr") self.assertEqual(1, len(capture)) w = capture[0] self.assertEqual(DeprecationWarning, w.category) self.assertEqual("star_jr", s.name) def test_warnings_emitted_instancemethod(self): zeon = ThingB() with warnings.catch_warnings(record=True) as capture: warnings.simplefilter("always") zeon.black_tristars() self.assertEqual(1, len(capture)) w = capture[0] self.assertEqual(DeprecationWarning, w.category) def test_pending_warnings_emitted_instancemethod(self): zeon = ThingB() with warnings.catch_warnings(record=True) as capture: warnings.simplefilter("always") zeon.blue_tristars() self.assertEqual(1, len(capture)) w = capture[0] self.assertEqual(PendingDeprecationWarning, w.category) def test_pending_warnings_emitted_classmethod(self): zeon = ThingB() with warnings.catch_warnings(record=True) as capture: warnings.simplefilter("always") zeon.yellow_wolf() self.assertEqual(1, len(capture)) w = capture[0] self.assertEqual(PendingDeprecationWarning, w.category) def test_warnings_emitted_classmethod(self): zeon = ThingB() with warnings.catch_warnings(record=True) as capture: warnings.simplefilter("always") zeon.white_wolf() self.assertEqual(1, len(capture)) w = capture[0] self.assertEqual(DeprecationWarning, w.category) def test_warnings_emitted_staticmethod(self): zeon = ThingB() with warnings.catch_warnings(record=True) as capture: warnings.simplefilter("always") zeon.blue_giant() self.assertEqual(1, len(capture)) w = capture[0] self.assertEqual(DeprecationWarning, w.category) def test_pending_warnings_emitted_staticmethod(self): zeon = ThingB() with warnings.catch_warnings(record=True) as capture: warnings.simplefilter("always") zeon.green_giant() self.assertEqual(1, len(capture)) w = capture[0] self.assertEqual(PendingDeprecationWarning, w.category) def test_removed_module(self): with warnings.catch_warnings(record=True) as capture: warnings.simplefilter("always") removals.removed_module(__name__) self.assertEqual(1, len(capture)) w = capture[0] self.assertEqual(DeprecationWarning, w.category) def test_pending_removed_module(self): with warnings.catch_warnings(record=True) as capture: warnings.simplefilter("always") removals.removed_module(__name__, category=PendingDeprecationWarning) self.assertEqual(1, len(capture)) w = capture[0] self.assertEqual(PendingDeprecationWarning, w.category) def test_removed_module_bad_type(self): self.assertRaises(TypeError, removals.removed_module, 2) debtcollector-2.3.0/debtcollector/updating.py000066400000000000000000000040771407402063100213600ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2015 Yahoo! Inc. All Rights Reserved. # # 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 wrapt from inspect import signature from debtcollector import _utils _KWARG_UPDATED_POSTFIX_TPL = (', please update the code to explicitly set %s ' 'as the value') _KWARG_UPDATED_PREFIX_TPL = ('The %s argument is changing its default value ' 'to %s') def updated_kwarg_default_value(name, old_value, new_value, message=None, version=None, stacklevel=3, category=FutureWarning): """Decorates a kwarg accepting function to change the default value""" prefix = _KWARG_UPDATED_PREFIX_TPL % (name, new_value) postfix = _KWARG_UPDATED_POSTFIX_TPL % old_value out_message = _utils.generate_message( prefix, postfix=postfix, message=message, version=version) def decorator(f): sig = signature(f) varnames = list(sig.parameters.keys()) @wrapt.decorator def wrapper(wrapped, instance, args, kwargs): explicit_params = set( varnames[:len(args)] + list(kwargs.keys()) ) allparams = set(varnames) default_params = set(allparams - explicit_params) if name in default_params: _utils.deprecation(out_message, stacklevel=stacklevel, category=category) return wrapped(*args, **kwargs) return wrapper(f) return decorator debtcollector-2.3.0/doc/000077500000000000000000000000001407402063100151035ustar00rootroot00000000000000debtcollector-2.3.0/doc/source/000077500000000000000000000000001407402063100164035ustar00rootroot00000000000000debtcollector-2.3.0/doc/source/conf.py000066400000000000000000000054621407402063100177110ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2020 Red Hat, Inc. # # 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 datetime import os import sys sys.path.insert(0, os.path.abspath('../..')) # -- General configuration ---------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.viewcode', 'sphinx.ext.doctest', 'openstackdocstheme', ] # openstackdocstheme options openstackdocs_repo_name = 'openstack/debtcollector' openstackdocs_auto_name = False openstackdocs_bug_project = 'debtcollector' openstackdocs_bug_tag = 'doc' # autodoc generation is a bit aggressive and a nuisance when doing heavy # text edit cycles. # execute "export SPHINX_DEBUG=1" in your terminal to disable # The suffix of source filenames. source_suffix = '.rst' # The master toctree document. master_doc = 'index' # General information about the project. project = 'debtcollector' copyright = '%s, OpenStack Foundation' % datetime.date.today().year # If true, '()' will be appended to :func: etc. cross-reference text. add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). add_module_names = True # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'native' # -- Options for HTML output -------------------------------------------------- # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. # html_theme_path = ["."] # html_theme = '_theme' html_theme = 'openstackdocs' # html_static_path = ['static'] # Output file base name for HTML help builder. htmlhelp_basename = '%sdoc' % project # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass # [howto/manual]). latex_documents = [ ('index', '%s.tex' % project, '%s Documentation' % project, 'OpenStack Foundation', 'manual'), ] # -- Options for autoddoc ---------------------------------------------------- # Keep source order autodoc_member_order = 'bysource' # Always include members autodoc_default_flags = ['members', 'show-inheritance'] debtcollector-2.3.0/doc/source/contributor/000077500000000000000000000000001407402063100207555ustar00rootroot00000000000000debtcollector-2.3.0/doc/source/contributor/index.rst000066400000000000000000000001171407402063100226150ustar00rootroot00000000000000============ Contributing ============ .. include:: ../../../CONTRIBUTING.rst debtcollector-2.3.0/doc/source/index.rst000066400000000000000000000012401407402063100202410ustar00rootroot00000000000000========================================= Welcome to debtcollector's documentation! ========================================= A collection of Python deprecation patterns and strategies that help you collect your technical debt in a non-destructive manner. .. note:: It should be noted that even though it is designed with OpenStack integration in mind, and that is where most of its *current* integration is it aims to be generally usable and useful in any project. .. toctree:: :maxdepth: 2 install/index user/index reference/index contributor/index .. rubric:: Indices and tables * :ref:`genindex` * :ref:`modindex` * :ref:`search` debtcollector-2.3.0/doc/source/install/000077500000000000000000000000001407402063100200515ustar00rootroot00000000000000debtcollector-2.3.0/doc/source/install/index.rst000066400000000000000000000003201407402063100217050ustar00rootroot00000000000000============ Installation ============ At the command line:: $ pip install debtcollector Or, if you have virtualenvwrapper installed:: $ mkvirtualenv debtcollector $ pip install debtcollector debtcollector-2.3.0/doc/source/reference/000077500000000000000000000000001407402063100203415ustar00rootroot00000000000000debtcollector-2.3.0/doc/source/reference/index.rst000066400000000000000000000012241407402063100222010ustar00rootroot00000000000000============= API Reference ============= The currently documented publicly exposed API's for usage in your project are defined below. .. warning:: External usage of internal utility functions and modules should be kept to a **minimum** as they may be altered, refactored or moved to other locations **without** notice (and without the typical deprecation cycle). Helpers ------- .. automodule:: debtcollector Moves ----- .. automodule:: debtcollector.moves Renames ------- .. automodule:: debtcollector.renames Removals -------- .. automodule:: debtcollector.removals Fixtures -------- .. automodule:: debtcollector.fixtures.disable debtcollector-2.3.0/doc/source/user/000077500000000000000000000000001407402063100173615ustar00rootroot00000000000000debtcollector-2.3.0/doc/source/user/history.rst000066400000000000000000000000401407402063100216060ustar00rootroot00000000000000.. include:: ../../../ChangeLog debtcollector-2.3.0/doc/source/user/index.rst000066400000000000000000000003261407402063100212230ustar00rootroot00000000000000=================== Using debtcollector =================== .. toctree:: :maxdepth: 2 usage .. history contains a lot of sections, toctree with maxdepth 1 is used. .. toctree:: :maxdepth: 1 history debtcollector-2.3.0/doc/source/user/usage.rst000066400000000000000000000300631407402063100212210ustar00rootroot00000000000000======== Examples ======== Removing a class/classmethod/method/function -------------------------------------------- To signal to a user that a method (staticmethod, classmethod, or regular instance method) or a class or function is going to be removed at some point in the future the :py:func:`~debtcollector.removals.remove` function/decorator can be used to achieve this in a non-destructive manner. A basic example to do just this (on a method/function): .. doctest:: >>> from debtcollector import removals >>> import warnings >>> warnings.simplefilter('always') >>> class Car(object): ... @removals.remove ... def start(self): ... pass ... >>> c = Car() >>> c.start() **Expected output:** .. testoutput:: __main__:1: DeprecationWarning: Using function/method 'Car.start()' is deprecated A basic example to do just this (on a class): .. doctest:: >>> from debtcollector import removals >>> import warnings >>> warnings.simplefilter('always') >>> @removals.removed_class("Pinto") ... class Pinto(object): ... pass ... >>> p = Pinto() **Expected output:** .. testoutput:: __main__:1: DeprecationWarning: Using class 'Pinto' (either directly or via inheritance) is deprecated A basic example to do just this (on a classmethod): .. doctest:: >>> from debtcollector import removals >>> import warnings >>> warnings.simplefilter("once") >>> class OldAndBusted(object): ... @removals.remove ... @classmethod ... def fix_things(cls): ... pass ... >>> OldAndBusted.fix_things() **Expected output:** .. testoutput:: __main__:1: DeprecationWarning: Using function/method 'OldAndBusted.fix_things()' is deprecated Removing a instance property ---------------------------- Use the :py:func:`~debtcollector.removals.removed_property` decorator to signal that an attribute of a class is deprecated. A basic example to do just this: .. doctest:: >>> import warnings >>> warnings.simplefilter("once") >>> from debtcollector import removals >>> class OldAndBusted(object): ... @removals.removed_property ... def thing(self): ... return 'old-and-busted' ... @thing.setter ... def thing(self, value): ... pass ... @thing.deleter ... def thing(self): ... pass ... >>> o = OldAndBusted() >>> o.thing 'old-and-busted' >>> o.thing = '2' >>> del o.thing .. testoutput:: __main__:1: DeprecationWarning: Reading the 'thing' property is deprecated __main__:1: DeprecationWarning: Setting the 'thing' property is deprecated __main__:1: DeprecationWarning: Deleting the 'thing' property is deprecated Removing a keyword argument --------------------------- A basic example to do just this (on a classmethod): .. doctest:: >>> import warnings >>> warnings.simplefilter("once") >>> from debtcollector import removals >>> class OldAndBusted(object): ... @removals.removed_kwarg('resp', message="Please use 'response' instead") ... @classmethod ... def factory(cls, resp=None, response=None): ... response = resp or response ... return response ... >>> OldAndBusted.factory(resp='super-duper') 'super-duper' .. testoutput:: __main__:1: DeprecationWarning: Using the 'resp' argument is deprecated: Please use 'response' instead A basic example to do just this (on a ``__init__`` method): .. doctest:: >>> import warnings >>> warnings.simplefilter("once") >>> from debtcollector import removals >>> class OldAndBusted(object): ... @removals.removed_kwarg('bleep') ... def __init__(self, bleep=None): ... self.bloop = bleep ... >>> o = OldAndBusted(bleep=2) .. testoutput:: __main__:1: DeprecationWarning: Using the 'bleep' argument is deprecated Changing the default value of a keyword argument ------------------------------------------------ A basic example to do just this: .. doctest:: >>> import warnings >>> warnings.simplefilter("once") >>> from debtcollector import updating >>> class OldAndBusted(object): ... ip = '127.0.0.1' ... @updating.updated_kwarg_default_value('type', 'http', 'https') ... def url(self, type='http'): ... response = '%s://%s' % (type, self.ip) ... return response ... >>> OldAndBusted().url() 'http://127.0.0.1' .. testoutput:: __main__:1: FutureWarning: The http argument is changing its default value to https, please update the code to explicitly set http as the value A basic classmethod example. .. note:: the @classmethod decorator is before the debtcollector one .. doctest:: >>> import warnings >>> warnings.simplefilter("once") >>> from debtcollector import updating >>> class OldAndBusted(object): ... ip = '127.0.0.1' ... @classmethod ... @updating.updated_kwarg_default_value('type', 'http', 'https') ... def url(cls, type='http'): ... response = '%s://%s' % (type, cls.ip) ... return response ... >>> OldAndBusted.url() 'http://127.0.0.1' .. testoutput:: __main__:1: FutureWarning: The http argument is changing its default value to https, please update the code to explicitly set http as the value Moving a function ----------------- To change the name or location of a regular function use the :py:func:`~debtcollector.moves.moved_function` function: .. doctest:: >>> from debtcollector import moves >>> import warnings >>> warnings.simplefilter('always') >>> def new_thing(): ... return "new thing" ... >>> old_thing = moves.moved_function(new_thing, 'old_thing', __name__) >>> new_thing() 'new thing' >>> old_thing() 'new thing' **Expected output:** .. testoutput:: __main__:1: DeprecationWarning: Function '__main__.old_thing()' has moved to '__main__.new_thing()' Moving a method --------------- To move a *instance* method from an existing one to a new one the :py:func:`~debtcollector.moves.moved_method` function/decorator can be used to achieve this in a non-destructive manner. A basic example to do just this: .. doctest:: >>> from debtcollector import moves >>> import warnings >>> warnings.simplefilter('always') >>> class Cat(object): ... @moves.moved_method('meow') ... def mewow(self): ... return self.meow() ... def meow(self): ... return 'kitty' ... >>> c = Cat() >>> c.mewow() 'kitty' >>> c.meow() 'kitty' **Expected output:** .. testoutput:: __main__:1: DeprecationWarning: Method 'Cat.mewow()' has moved to 'Cat.meow()' Moving a property ----------------- To move a *instance* property from an existing one to a new one the :py:func:`~debtcollector.moves.moved_property` function/decorator can be used to achieve this in a non-destructive manner. A basic example to do just this: .. doctest:: >>> from debtcollector import moves >>> import warnings >>> warnings.simplefilter('always') >>> class Dog(object): ... @property ... @moves.moved_property('bark') ... def burk(self): ... return self.bark ... @property ... def bark(self): ... return 'woof' ... >>> d = Dog() >>> d.burk 'woof' >>> d.bark 'woof' **Expected output:** .. testoutput:: __main__:1: DeprecationWarning: Property 'Dog.burk' has moved to 'Dog.bark' Moving a class -------------- To move a *class* from an existing one to a new one the :py:func:`~debtcollector.moves.moved_class` type generator function can be used to achieve this in a non-destructive manner. A basic example to do just this: .. doctest:: >>> from debtcollector import moves >>> import warnings >>> warnings.simplefilter('always') >>> class WizBang(object): ... pass ... >>> OldWizBang = moves.moved_class(WizBang, 'OldWizBang', __name__) >>> a = OldWizBang() >>> b = WizBang() **Expected output:** .. testoutput:: __main__:1: DeprecationWarning: Class '__main__.OldWizBang' has moved to '__main__.WizBang' Renaming a keyword argument --------------------------- To notify the user when a keyword argument has been replaced with a new and improved keyword argument and the user is still using the old keyword argument the :py:func:`~debtcollector.renames.renamed_kwarg` function/decorator can be used to achieve this in a non-destructive manner. A basic example to do just this: .. doctest:: >>> from debtcollector import renames >>> import warnings >>> warnings.simplefilter('always') >>> @renames.renamed_kwarg('snizzle', 'nizzle') ... def do_the_deed(snizzle=True, nizzle=True): ... return (snizzle, nizzle) ... >>> do_the_deed() (True, True) >>> do_the_deed(snizzle=False) (False, True) >>> do_the_deed(nizzle=False) (True, False) **Expected output:** .. testoutput:: __main__:1: DeprecationWarning: Using the 'snizzle' argument is deprecated, please use the 'nizzle' argument instead Further customizing the emitted messages ---------------------------------------- It is typically useful to tell the user when a deprecation has started and when the deprecated item will be officially removed (deleted or other). To enable this all the currently provided functions this library provides take a ``message``, ``version`` and ``removal_version`` keyword arguments. These are used in forming the message that is shown to the user when they trigger the deprecated activity. A basic example to do just this: .. doctest:: >>> from debtcollector import renames >>> import warnings >>> warnings.simplefilter('always') >>> @renames.renamed_kwarg('snizzle', 'nizzle', version="0.5", removal_version="0.7") ... def do_the_deed(snizzle=True, nizzle=True): ... pass ... >>> do_the_deed(snizzle=False) **Expected output:** .. testoutput:: __main__:1: DeprecationWarning: Using the 'snizzle' argument is deprecated in version '0.5' and will be removed in version '0.7', please use the 'nizzle' argument instead If the ``removal_version`` is unknown the special character ``?`` can be used instead (to denote that the deprecated activity will be removed sometime in the future). A basic example to do just this: .. doctest:: >>> from debtcollector import renames >>> import warnings >>> warnings.simplefilter('always') >>> @renames.renamed_kwarg('snizzle', 'nizzle', version="0.5", removal_version="?") ... def do_the_deed(snizzle=True, nizzle=True): ... pass ... >>> do_the_deed(snizzle=False) **Expected output:** .. testoutput:: __main__:1: DeprecationWarning: Using the 'snizzle' argument is deprecated in version '0.5' and will be removed in a future version, please use the 'nizzle' argument instead To further customize the message (with a special postfix) the ``message`` keyword argument can be provided. A basic example to do just this: .. doctest:: >>> from debtcollector import renames >>> import warnings >>> warnings.simplefilter('always') >>> @renames.renamed_kwarg('snizzle', 'nizzle', message="Pretty please stop using it") ... def do_the_deed(snizzle=True, nizzle=True): ... pass ... >>> do_the_deed(snizzle=False) **Expected output:** .. testoutput:: __main__:1: DeprecationWarning: Using the 'snizzle' argument is deprecated, please use the 'nizzle' argument instead: Pretty please stop using it Deprecating anything else ------------------------- For use-cases which do not fit the above decorators, properties other provided functionality the final option is to use debtcollectors the :py:func:`~debtcollector.deprecate` function to make your own messages (using the message building logic that debtcollector uses itself). A basic example to do just this: .. doctest:: >>> import warnings >>> warnings.simplefilter("always") >>> import debtcollector >>> debtcollector.deprecate("This is no longer supported", version="1.0") .. testoutput:: __main__:1: DeprecationWarning: This is no longer supported in version '1.0' debtcollector-2.3.0/releasenotes/000077500000000000000000000000001407402063100170275ustar00rootroot00000000000000debtcollector-2.3.0/releasenotes/notes/000077500000000000000000000000001407402063100201575ustar00rootroot00000000000000debtcollector-2.3.0/releasenotes/notes/add-reno-996dd44974d53238.yaml000066400000000000000000000000721407402063100245550ustar00rootroot00000000000000--- other: - Introduce reno for deployer release notes. debtcollector-2.3.0/releasenotes/notes/drop-python-2-7-73d3113c69d724d6.yaml000066400000000000000000000002141407402063100257060ustar00rootroot00000000000000--- upgrade: - | Python 2.7 support has been dropped. The minimum version of Python now supported by debtcollector is Python 3.6. debtcollector-2.3.0/releasenotes/source/000077500000000000000000000000001407402063100203275ustar00rootroot00000000000000debtcollector-2.3.0/releasenotes/source/_static/000077500000000000000000000000001407402063100217555ustar00rootroot00000000000000debtcollector-2.3.0/releasenotes/source/_static/.placeholder000066400000000000000000000000001407402063100242260ustar00rootroot00000000000000debtcollector-2.3.0/releasenotes/source/_templates/000077500000000000000000000000001407402063100224645ustar00rootroot00000000000000debtcollector-2.3.0/releasenotes/source/_templates/.placeholder000066400000000000000000000000001407402063100247350ustar00rootroot00000000000000debtcollector-2.3.0/releasenotes/source/conf.py000066400000000000000000000213711407402063100216320ustar00rootroot00000000000000# -*- coding: utf-8 -*- # 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. # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'openstackdocstheme', 'reno.sphinxext', ] # openstackdocstheme options openstackdocs_repo_name = 'openstack/debtcollector' openstackdocs_auto_name = False openstackdocs_bug_project = 'debtcollector' openstackdocs_bug_tag = 'doc' # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = 'debtcollector Release Notes' copyright = '2016, debtcollector Developers' # Release notes do not need a version number in the title, they # cover multiple releases. # The full version, including alpha/beta/rc tags. release = '' # The short X.Y version. version = '' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # today = '' # Else, today_fmt is used as the format for a strftime call. # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [] # The reST default role (used for this markup: `text`) to use for all # documents. # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'native' # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. # keep_warnings = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'openstackdocs' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". # html_title = None # A shorter title for the navigation bar. Default is the same as html_title. # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. # html_extra_path = [] # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # html_additional_pages = {} # If false, no module index is generated. # html_domain_indices = True # If false, no index is generated. # html_use_index = True # If true, the index is split into individual pages for each letter. # html_split_index = False # If true, links to the reST sources are added to the pages. # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'debtcollectorReleaseNotesDoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # 'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ ('index', 'debtcollectorReleaseNotes.tex', 'debtcollector Release Notes Documentation', 'debtcollector Developers', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # latex_use_parts = False # If true, show page references after internal links. # latex_show_pagerefs = False # If true, show URL addresses after external links. # latex_show_urls = False # Documents to append as an appendix to all manuals. # latex_appendices = [] # If false, no module index is generated. # latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'debtcollectorReleaseNotes', 'debtcollector Release Notes Documentation', ['debtcollector Developers'], 1) ] # If true, show URL addresses after external links. # man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'debtcollectorReleaseNotes', 'debtcollector Release Notes Documentation', 'debtcollector Developers', 'debtcollectorReleaseNotes', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. # texinfo_appendices = [] # If false, no module index is generated. # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. # texinfo_no_detailmenu = False # -- Options for Internationalization output ------------------------------ locale_dirs = ['locale/'] debtcollector-2.3.0/releasenotes/source/index.rst000066400000000000000000000003221407402063100221650ustar00rootroot00000000000000============================= debtcollector Release Notes ============================= .. toctree:: :maxdepth: 1 unreleased victoria ussuri train stein rocky queens pike ocata debtcollector-2.3.0/releasenotes/source/ocata.rst000066400000000000000000000002301407402063100221430ustar00rootroot00000000000000=================================== Ocata Series Release Notes =================================== .. release-notes:: :branch: origin/stable/ocata debtcollector-2.3.0/releasenotes/source/pike.rst000066400000000000000000000002171407402063100220110ustar00rootroot00000000000000=================================== Pike Series Release Notes =================================== .. release-notes:: :branch: stable/pike debtcollector-2.3.0/releasenotes/source/queens.rst000066400000000000000000000002231407402063100223560ustar00rootroot00000000000000=================================== Queens Series Release Notes =================================== .. release-notes:: :branch: stable/queens debtcollector-2.3.0/releasenotes/source/rocky.rst000066400000000000000000000002211407402063100222030ustar00rootroot00000000000000=================================== Rocky Series Release Notes =================================== .. release-notes:: :branch: stable/rocky debtcollector-2.3.0/releasenotes/source/stein.rst000066400000000000000000000002211407402063100221760ustar00rootroot00000000000000=================================== Stein Series Release Notes =================================== .. release-notes:: :branch: stable/stein debtcollector-2.3.0/releasenotes/source/train.rst000066400000000000000000000001761407402063100222020ustar00rootroot00000000000000========================== Train Series Release Notes ========================== .. release-notes:: :branch: stable/train debtcollector-2.3.0/releasenotes/source/unreleased.rst000066400000000000000000000001441407402063100232070ustar00rootroot00000000000000========================== Unreleased Release Notes ========================== .. release-notes:: debtcollector-2.3.0/releasenotes/source/ussuri.rst000066400000000000000000000002021407402063100224050ustar00rootroot00000000000000=========================== Ussuri Series Release Notes =========================== .. release-notes:: :branch: stable/ussuri debtcollector-2.3.0/releasenotes/source/victoria.rst000066400000000000000000000002121407402063100226740ustar00rootroot00000000000000============================= Victoria Series Release Notes ============================= .. release-notes:: :branch: stable/victoria debtcollector-2.3.0/requirements.txt000066400000000000000000000005341407402063100176240ustar00rootroot00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. pbr!=2.1.0,>=2.0.0 # Apache-2.0 importlib_metadata>=1.7.0;python_version<'3.8' # Apache-2.0 six>=1.10.0 # MIT wrapt>=1.7.0 # BSD License debtcollector-2.3.0/setup.cfg000066400000000000000000000016071407402063100161630ustar00rootroot00000000000000[metadata] name = debtcollector summary = A collection of Python deprecation patterns and strategies that help you collect your technical debt in a non-destructive manner. description_file = README.rst author = OpenStack author_email = openstack-discuss@lists.openstack.org home_page = https://docs.openstack.org/debtcollector/latest python_requires = >=3.6 classifier = Environment :: OpenStack Intended Audience :: Information Technology Intended Audience :: System Administrators License :: OSI Approved :: Apache Software License Operating System :: POSIX :: Linux Programming Language :: Python Programming Language :: Python :: 3 Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 Programming Language :: Python :: 3 :: Only Programming Language :: Python :: Implementation :: CPython [files] packages = debtcollector debtcollector-2.3.0/setup.py000066400000000000000000000013761407402063100160570ustar00rootroot00000000000000# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. # # 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. # THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT import setuptools setuptools.setup( setup_requires=['pbr>=2.0.0'], pbr=True) debtcollector-2.3.0/test-requirements.txt000066400000000000000000000010361407402063100205770ustar00rootroot00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. hacking>=3.0,<3.1.0 # Apache-2.0 coverage!=4.4,>=4.0 # Apache-2.0 python-subunit>=1.0.0 # Apache-2.0/BSD sphinx>=2.0.0,!=2.1.0 # BSD openstackdocstheme>=2.2.1 # Apache-2.0 stestr>=2.0.0 # Apache-2.0 testtools>=2.2.0 # MIT fixtures>=3.0.0 # Apache-2.0/BSD doc8>=0.8.1 # Apache-2.0 reno>=3.1.0 # Apache-2.0 pre-commit>=2.6.0 # MIT debtcollector-2.3.0/tox.ini000066400000000000000000000022771407402063100156610ustar00rootroot00000000000000[tox] minversion = 3.2.0 envlist = py3,pep8 ignore_basepython_conflict = True [testenv] basepython = python3 setenv = VIRTUAL_ENV={envdir} deps = -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/test-requirements.txt -r{toxinidir}/requirements.txt commands = stestr run --slowest {posargs} [testenv:debug] commands = oslo_debug_helper {posargs} [testenv:pep8] commands = pre-commit run -a sphinx-build -b doctest doc/source doc/build doc8 --ignore-path "doc/source/history.rst" doc/source [testenv:venv] commands = {posargs} [testenv:cover] setenv = PYTHON=coverage run --source $project --parallel-mode commands = stestr run {posargs} coverage combine coverage html -d cover coverage xml -o cover/coverage.xml [testenv:docs] commands = sphinx-build -W --keep-going -b html -d doc/build/doctrees doc/source doc/build/html [flake8] # E123, E125 skipped as they are invalid PEP-8. show-source = True ignore = E123,E125 exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build [testenv:releasenotes] commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html