diff options
author | Isaac Muse <faceless.shop@gmail.com> | 2018-10-25 07:43:27 -0600 |
---|---|---|
committer | Waylan Limberg <waylan.limberg@icloud.com> | 2018-10-25 09:43:27 -0400 |
commit | d81207a6bd2a60c39b971baf789f8ac7fa912a21 (patch) | |
tree | c35fcfad8e76504635a4df5dd1cedb581fddf02d | |
parent | d9ee723c5cbfae4f68d66cc18414e7062e8afed4 (diff) | |
download | python-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.md | 9 | ||||
-rw-r--r-- | markdown/__init__.py | 29 | ||||
-rw-r--r-- | markdown/util.py | 42 | ||||
-rw-r--r-- | tests/test_apis.py | 39 |
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) |