summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIsaac Muse <faceless.shop@gmail.com>2018-10-25 07:43:27 -0600
committerWaylan Limberg <waylan.limberg@icloud.com>2018-10-25 09:43:27 -0400
commitd81207a6bd2a60c39b971baf789f8ac7fa912a21 (patch)
treec35fcfad8e76504635a4df5dd1cedb581fddf02d
parentd9ee723c5cbfae4f68d66cc18414e7062e8afed4 (diff)
downloadpython-markdown-d81207a6bd2a60c39b971baf789f8ac7fa912a21.tar.gz
Deprecate version and version_info (#740)
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 #739.
-rw-r--r--docs/change_log/release-3.0.md9
-rw-r--r--markdown/__init__.py29
-rw-r--r--markdown/util.py42
-rw-r--r--tests/test_apis.py39
4 files changed, 114 insertions, 5 deletions
diff --git a/docs/change_log/release-3.0.md b/docs/change_log/release-3.0.md
index ab6b83e..74a734f 100644
--- a/docs/change_log/release-3.0.md
+++ b/docs/change_log/release-3.0.md
@@ -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
diff --git a/markdown/__init__.py b/markdown/__init__.py
index b39202d..ea2bbf4 100644
--- a/markdown/__init__.py
+++ b/markdown/__init__.py
@@ -23,6 +23,7 @@ License: BSD (see LICENSE.md for details).
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...
@@ -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__)
diff --git a/markdown/util.py b/markdown/util.py
index 262521c..fc56ba2 100644
--- a/markdown/util.py
+++ b/markdown/util.py
@@ -113,7 +113,7 @@ AUXILIARY GLOBAL FUNCTIONS
"""
-def deprecated(message):
+def deprecated(message, stacklevel=2):
"""
Raise a DeprecationWarning when wrapped function/method is called.
@@ -125,7 +125,7 @@ def deprecated(message):
warnings.warn(
"'{}' is deprecated. {}".format(func.__name__, message),
category=DeprecationWarning,
- stacklevel=2
+ stacklevel=stacklevel
)
return func(*args, **kwargs)
return deprecated_func
@@ -177,6 +177,44 @@ MISC AUXILIARY CLASSES
"""
+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
diff --git a/tests/test_apis.py b/tests/test_apis.py
index 20144e4..a9fa770 100644
--- a/tests/test_apis.py
+++ b/tests/test_apis.py
@@ -1004,3 +1004,42 @@ Some +test+ and a [+link+](http://test.com)
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)