Skip to content

Commit

Permalink
Deprecate version and version_info (Python-Markdown#740)
Browse files Browse the repository at this point in the history
This essentially implements the closest we can get to PEP 562 which allows for modules to control `__dir__` and `__getattr__` in order to deprecate attributes. Here we provide a wrapper class for the module in `util`. If a module has attributes that need to deprecated, we derive from the wrapper class and define the attributes as functions with the `property` decorator and the provided `deprecated` decorator. The class is instantiated with the module's `__name__` attribute and the class will properly replace the module with the wrapped module.  When accessing the depracted attributes, a warning is raised. Closes Python-Markdown#739.
  • Loading branch information
facelessuser authored and waylan committed Oct 25, 2018
1 parent d9ee723 commit d81207a
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 5 deletions.
9 changes: 9 additions & 0 deletions docs/change_log/release-3.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,15 @@ held within the `Markdown` class instance, access to the globals is no longer
necessary and any extensions which expect the keyword will raise a
`DeprecationWarning`. A future release will raise an error.

### `markdown.version` and `markdown.version_info` deprecated

Historically, version numbers where acquired via the attributes `markdown.version`
and `markdown.version_info`. Moving forward, a more standardized approach is being
followed and versions are acquired via the `markdown.__version__` and
`markdown.__version_info__` attributes. The legacy attributes are still available
to allow distinguishing versions between the legacy Markdown 2.0 series and the
Markdown 3.0 series, but in the future the legacy attributes will be removed.

### Added new, more flexible `InlineProcessor` class

A new `InlineProcessor` class handles inline processing much better and allows
Expand Down
29 changes: 26 additions & 3 deletions markdown/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from __future__ import absolute_import
from __future__ import unicode_literals
from .core import Markdown, markdown, markdownFromFile
from .util import ModuleWrap, deprecated
from pkg_resources.extern import packaging

# For backward compatibility as some extensions expect it...
Expand Down Expand Up @@ -63,6 +64,28 @@ def _get_version(): # pragma: no cover

__version__ = _get_version()

# Also support `version` for backward-compatabillity with <3.0 versions
version_info = __version_info__
version = __version__

class _ModuleWrap(ModuleWrap):
"""
Wrap module so that we can control `__getattribute__` and `__dir__` logic.
Treat `version` and `version_info` as deprecated properties.
Provides backward-compatabillity with <3.0 versions.
"""

@property
@deprecated("Use '__version__' instead.", stacklevel=3)
def version(self):
"""Get deprecated version."""

return __version__

@property
@deprecated("Use '__version_info__' instead.", stacklevel=3)
def version_info(self):
"""Get deprecated version info."""

return __version_info__


_ModuleWrap(__name__)
42 changes: 40 additions & 2 deletions markdown/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@
"""


def deprecated(message):
def deprecated(message, stacklevel=2):
"""
Raise a DeprecationWarning when wrapped function/method is called.
Expand All @@ -125,7 +125,7 @@ def deprecated_func(*args, **kwargs):
warnings.warn(
"'{}' is deprecated. {}".format(func.__name__, message),
category=DeprecationWarning,
stacklevel=2
stacklevel=stacklevel
)
return func(*args, **kwargs)
return deprecated_func
Expand Down Expand Up @@ -177,6 +177,44 @@ def code_escape(text):
"""


class ModuleWrap(object):
"""
Provided so that we can deprecate old version methodology.
See comments from Guido: <https://mail.python.org/pipermail/python-ideas/2012-May/014969.html>
and see PEP 562 which this is essentially a backport of: <https://www.python.org/dev/peps/pep-0562/>.
"""

def __init__(self, module):
"""Initialize."""

self._module = sys.modules[module]
sys.modules[module] = self

def __dir__(self):
"""
Implement the `dir` command.
Return module's results for the `dir` command along with any
attributes that have been added to the class.
"""

attr = (
set(dir(super(ModuleWrap, self).__getattribute__('_module'))) |
(set(self.__class__.__dict__.keys()) - set(ModuleWrap.__dict__.keys()))
)

return sorted(list(attr))

def __getattribute__(self, name):
"""Get the class attribute first and fallback to the module if not available."""

try:
return super(ModuleWrap, self).__getattribute__(name)
except AttributeError:
return getattr(super(ModuleWrap, self).__getattribute__('_module'), name)


class AtomicString(text_type):
"""A string which should not be further processed."""
pass
Expand Down
39 changes: 39 additions & 0 deletions tests/test_apis.py
Original file line number Diff line number Diff line change
Expand Up @@ -1004,3 +1004,42 @@ def test_ancestors_tail(self):

self.md.reset()
self.assertEqual(self.md.convert(test), result)


class TestGeneralDeprecations(unittest.TestCase):
"""Test general deprecations."""

def test_version_deprecation(self):
"""Test that version is deprecated."""

with warnings.catch_warnings(record=True) as w:
# Cause all warnings to always be triggered.
warnings.simplefilter("always")
# Trigger a warning.
version = markdown.version
# Verify some things
self.assertTrue(len(w) == 1)
self.assertTrue(issubclass(w[-1].category, DeprecationWarning))
self.assertEqual(version, markdown.__version__)

def test_version_info_deprecation(self):
"""Test that version info is deprecated."""

with warnings.catch_warnings(record=True) as w:
# Cause all warnings to always be triggered.
warnings.simplefilter("always")
# Trigger a warning.
version_info = markdown.version_info
# Verify some things
self.assertTrue(len(w) == 1)
self.assertTrue(issubclass(w[-1].category, DeprecationWarning))
self.assertEqual(version_info, markdown.__version_info__)

def test_deprecation_wrapper_dir(self):
"""Tests the `__dir__` attribute of the class as it replaces the module's."""

dir_attr = dir(markdown)
self.assertTrue('version' in dir_attr)
self.assertTrue('__version__' in dir_attr)
self.assertTrue('version_info' in dir_attr)
self.assertTrue('__version_info__' in dir_attr)

0 comments on commit d81207a

Please sign in to comment.