Retire Packaging Deb project repos

This commit is part of a series to retire the Packaging Deb
project. Step 2 is to remove all content from the project
repos, replacing it with a README notification where to find
ongoing work, and how to recover the repo if needed at some
future point (as in
https://docs.openstack.org/infra/manual/drivers.html#retiring-a-project).

Change-Id: Ic04aad26ca5b18ec7e49be38ed1b39b2651c58c3
This commit is contained in:
Tony Breeds 2017-09-12 15:57:36 -06:00
parent cd6ba6b122
commit 4a012a2fab
43 changed files with 14 additions and 3019 deletions

View File

@ -1,7 +0,0 @@
[run]
branch = True
source = debtcollector
omit = debtcollector/tests/*,debtcollector/openstack/*
[report]
ignore_errors = True

60
.gitignore vendored
View File

@ -1,60 +0,0 @@
*.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
nosetests.xml
.testrepository
.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

View File

@ -1,4 +0,0 @@
[gerrit]
host=review.openstack.org
port=29418
project=openstack/debtcollector.git

View File

@ -1,3 +0,0 @@
# Format is:
# <preferred e-mail> <other e-mail 1>
# <preferred e-mail> <other e-mail 2>

View File

@ -1,7 +0,0 @@
[DEFAULT]
test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \
OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \
OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \
${PYTHON:-python} -m subunit.run discover -t ./ . $LISTOPT $IDOPTION
test_id_option=--load-list $IDFILE
test_list_option=--list

View File

@ -1,13 +0,0 @@
If you would like to contribute to the development of OpenStack,
you must follow the steps in this page:
http://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:
http://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

View File

@ -1,4 +0,0 @@
debtcollector Style Commandments
===============================================
Read the OpenStack Style Commandments https://docs.openstack.org/hacking/latest

175
LICENSE
View File

@ -1,175 +0,0 @@
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.

14
README Normal file
View File

@ -0,0 +1,14 @@
This project is no longer maintained.
The contents of this repository are still available in the Git
source code management system. To see the contents of this
repository before it reached its end of life, please check out the
previous commit with "git checkout HEAD^1".
For ongoing work on maintaining OpenStack packages in the Debian
distribution, please see the Debian OpenStack packaging team at
https://wiki.debian.org/OpenStack/.
For any further questions, please email
openstack-dev@lists.openstack.org or join #openstack-dev on
Freenode.

View File

@ -1,34 +0,0 @@
========================
Team and repository tags
========================
.. image:: http://governance.openstack.org/badges/debtcollector.svg
:target: http://governance.openstack.org/reference/tags/index.html
.. Change things from this point on
Debtcollector
=============
.. image:: https://img.shields.io/pypi/v/debtcollector.svg
:target: https://pypi.python.org/pypi/debtcollector/
:alt: Latest Version
.. image:: https://img.shields.io/pypi/dm/debtcollector.svg
:target: https://pypi.python.org/pypi/debtcollector/
:alt: Downloads
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://git.openstack.org/cgit/openstack/debtcollector
* Bugs: https://bugs.launchpad.net/debtcollector

View File

@ -1 +0,0 @@
[python: **.py]

View File

@ -1,48 +0,0 @@
# -*- 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.
import pbr.version
from debtcollector import _utils
__version__ = pbr.version.VersionInfo(
'debtcollector').version_string()
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)

View File

@ -1,180 +0,0 @@
# -*- 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)

View File

@ -1,39 +0,0 @@
# -*- 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.
from __future__ import absolute_import
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():
<some code that calls into depreciated code>
"""
def _setUp(self):
self.addCleanup(setattr, _utils, "_enabled", True)
_utils._enabled = False

View File

@ -1,197 +0,0 @@
# -*- 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

View File

@ -1,334 +0,0 @@
# 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)

View File

@ -1,45 +0,0 @@
# -*- 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

View File

@ -1,23 +0,0 @@
# -*- 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."""

View File

@ -1,664 +0,0 @@
# 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.getargspec(KittyKat.supermeow),
inspect.getargspec(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.getargspec(blip_blop_unwrapped),
inspect.getargspec(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.getargspec(blip_blop_blip_unwrapped),
inspect.getargspec(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.getargspec(crimson_lightning_unwrapped),
inspect.getargspec(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.getargspec(f_unwrapped),
inspect.getargspec(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)

View File

@ -1,68 +0,0 @@
# -*- 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 six
import wrapt
if six.PY3:
import inspect
Parameter = inspect.Parameter
Signature = inspect.Signature
get_signature = inspect.signature
else:
# Provide an equivalent but use funcsigs instead...
import funcsigs
Parameter = funcsigs.Parameter
Signature = funcsigs.Signature
get_signature = funcsigs.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 = get_signature(f)
varnames = list(six.iterkeys(sig.parameters))
@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

View File

@ -1,93 +0,0 @@
# -*- 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.
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
repository_name = 'openstack/debtcollector'
bug_project = 'debtcollector'
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 = u'debtcollector'
copyright = u'%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 = 'sphinx'
# -- 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']
html_last_updated_fmt = '%Y-%m-%d %H:%M'
# 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,
u'%s Documentation' % project,
u'OpenStack Foundation', 'manual'),
]
# Example configuration for intersphinx: refer to the Python standard library.
#intersphinx_mapping = {'http://docs.python.org/': None}
# -- Options for autoddoc ----------------------------------------------------
# Keep source order
autodoc_member_order = 'bysource'
# Always include members
autodoc_default_flags = ['members', 'show-inheritance']

View File

@ -1,5 +0,0 @@
============
Contributing
============
.. include:: ../../../CONTRIBUTING.rst

View File

@ -1,27 +0,0 @@
=========================================
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`

View File

@ -1,12 +0,0 @@
============
Installation
============
At the command line::
$ pip install debtcollector
Or, if you have virtualenvwrapper installed::
$ mkvirtualenv debtcollector
$ pip install debtcollector

View File

@ -1,38 +0,0 @@
=============
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

View File

@ -1 +0,0 @@
.. include:: ../../../ChangeLog

View File

@ -1,14 +0,0 @@
===================
Using debtcollector
===================
.. toctree::
:maxdepth: 2
usage
.. history contains a lot of sections, toctree with maxdepth 1 is used.
.. toctree::
:maxdepth: 1
history

View File

@ -1,447 +0,0 @@
========
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'

View File

@ -1,3 +0,0 @@
---
other:
- Introduce reno for deployer release notes.

View File

@ -1,281 +0,0 @@
# -*- 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
repository_name = 'openstack/debtcollector'
bug_project = 'debtcollector'
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 = u'debtcollector Release Notes'
copyright = u'2016, debtcollector Developers'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
# The full version, including alpha/beta/rc tags.
import pkg_resources
release = pkg_resources.get_distribution('debtcollector').version
# The short X.Y version.
version = release
# 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 = 'sphinx'
# 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
# "<project> v<release> 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 not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
html_last_updated_fmt = '%Y-%m-%d %H:%M'
# 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 <link> 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',
u'debtcollector Release Notes Documentation',
u'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',
u'debtcollector Release Notes Documentation',
[u'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',
u'debtcollector Release Notes Documentation',
u'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/']

View File

@ -1,9 +0,0 @@
=============================
debtcollector Release Notes
=============================
.. toctree::
:maxdepth: 1
unreleased
ocata

View File

@ -1,6 +0,0 @@
===================================
Ocata Series Release Notes
===================================
.. release-notes::
:branch: origin/stable/ocata

View File

@ -1,5 +0,0 @@
==========================
Unreleased Release Notes
==========================
.. release-notes::

View File

@ -1,8 +0,0 @@
# 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
six>=1.9.0 # MIT
wrapt>=1.7.0 # BSD License
funcsigs>=0.4;python_version=='2.7' or python_version=='2.6' # Apache-2.0

View File

@ -1,35 +0,0 @@
[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-dev@lists.openstack.org
home-page = https://docs.openstack.org/debtcollector/latest
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 :: 2
Programming Language :: Python :: 2.7
Programming Language :: Python :: 3
Programming Language :: Python :: 3.5
[files]
packages =
debtcollector
[build_sphinx]
source-dir = doc/source
build-dir = doc/build
all_files = 1
warning-is-error = 1
[upload_sphinx]
upload-dir = doc/build/html
[wheel]
universal = 1

View File

@ -1,29 +0,0 @@
# 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
# In python < 2.7.4, a lazy loading of package `pbr` will break
# setuptools if some other modules registered functions in `atexit`.
# solution from: http://bugs.python.org/issue15881#msg170215
try:
import multiprocessing # noqa
except ImportError:
pass
setuptools.setup(
setup_requires=['pbr>=2.0.0'],
pbr=True)

View File

@ -1,15 +0,0 @@
# 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<0.11,>=0.10.0
coverage!=4.4,>=4.0 # Apache-2.0
python-subunit>=0.0.18 # Apache-2.0/BSD
sphinx>=1.6.2 # BSD
openstackdocstheme>=1.11.0 # Apache-2.0
testrepository>=0.0.18 # Apache-2.0/BSD
testtools>=1.4.0 # MIT
fixtures>=3.0.0 # Apache-2.0/BSD
doc8 # Apache-2.0
reno!=2.3.1,>=1.8.0 # Apache-2.0

View File

@ -1,30 +0,0 @@
#!/usr/bin/env bash
# Client constraint file contains this client version pin that is in conflict
# with installing the client from source. We should remove the version pin in
# the constraints file before applying it for from-source installation.
CONSTRAINTS_FILE="$1"
shift 1
set -e
# NOTE(tonyb): Place this in the tox enviroment's log dir so it will get
# published to logs.openstack.org for easy debugging.
localfile="$VIRTUAL_ENV/log/upper-constraints.txt"
if [[ "$CONSTRAINTS_FILE" != http* ]]; then
CONSTRAINTS_FILE="file://$CONSTRAINTS_FILE"
fi
# NOTE(tonyb): need to add curl to bindep.txt if the project supports bindep
curl "$CONSTRAINTS_FILE" --insecure --progress-bar --output "$localfile"
pip install -c"$localfile" openstack-requirements
# This is the main purpose of the script: Allow local installation of
# the current repo. It is listed in constraints file and thus any
# install will be constrained and we need to unconstrain it.
edit-constraints "$localfile" -- "$CLIENT_NAME"
pip install -c"$localfile" -U "$@"
exit $?

55
tox.ini
View File

@ -1,55 +0,0 @@
[tox]
minversion = 2.0
envlist = py35,py27,pypy,pep8
[testenv]
setenv =
VIRTUAL_ENV={envdir}
BRANCH_NAME=master
CLIENT_NAME=debtcollector
install_command = {toxinidir}/tools/tox_install.sh {env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages}
deps = -r{toxinidir}/test-requirements.txt
commands = python setup.py testr --slowest --testr-args='{posargs}'
[testenv:debug]
commands = oslo_debug_helper {posargs}
[testenv:debug-py27]
basepython = python2.7
commands = oslo_debug_helper {posargs}
[testenv:debug-py34]
basepython = python3.4
commands = oslo_debug_helper {posargs}
[testenv:debug-py35]
basepython = python3.5
commands = oslo_debug_helper {posargs}
[testenv:pep8]
commands = flake8
[testenv:venv]
commands = {posargs}
[testenv:cover]
commands = python setup.py testr --coverage --testr-args='{posargs}'
[testenv:docs]
commands = python setup.py build_sphinx
[testenv:py27]
deps = {[testenv]deps}
commands =
python setup.py testr --slowest --testr-args='{posargs}'
sphinx-build -b doctest doc/source doc/build
doc8 --ignore-path "doc/source/history.rst" doc/source
[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