Repository: Python-Markdown/markdown (python)

Issue:
Possible old version deprecation path
This is just a proposal.  I think it is a neat idea and could lead us to a path to deprecate old versions. Is this something worth doing? I'll leave that up to the team to decide, I just thought this was interesting.

So let me start by saying, this idea is actually from something that I read Guido said that they purposely allowed in the import system: https://mail.python.org/pipermail/python-ideas/2012-May/014969.html.  More elaborate implementations have been implemented in libraries such as Twisted, but for our purposes, a simple implementation based off Guido's remark would be all we would need.

The reason why deprecating attributes is difficult is because Python doesn't support `__getattributes__` on modules (before Python 3.7). So to get this functionality you'd have to wrap the module in a class.  Now I know what your thinking, that sounds like hackery, but see Guido's link above. So here's the idea, in `util` add the following class (I've also modified the decorator wrap to allow specifying the stacklevel as we have an additional layer of abstraction).

Notice below that we wrap the class, and first look in the class for attributes we desire, if we fail, we just get it from the module.

util.py

```py
def deprecated(message, stacklevel=2):  # pragma: no cover
    """
    Raise a `DeprecationWarning` when wrapped function/method is called.

    Borrowed from https://stackoverflow.com/a/48632082/866026
    """

    def deprecated_decorator(func):
        """Deprecation decorator."""

        @wraps(func)
        def deprecated_func(*args, **kwargs):
            """Display deprecation warning."""

            warnings.warn(
                "'{}' is deprecated. {}".format(func.__name__, message),
                category=DeprecationWarning,
                stacklevel=stacklevel
            )
            return func(*args, **kwargs)
        return deprecated_func
    return deprecated_decorator


class ModuleWrap(object):
    """
    Provided so that we can deprecate old version methodology.

    https://mail.python.org/pipermail/python-ideas/2012-May/014969.html. 
    """

    def __init__(self, module):
        """Initialize."""

        self._module = sys.modules[module]
        sys.modules[module] = self

    def __getattribute__(self, name):
        """Get the class attribute first, and fallback to the module."""

        try:
            return super(ModuleWrap, self).__getattribute__(name)
        except AttributeError:
            return getattr(super(ModuleWrap, self).__getattribute__('_module'), name)
```

Next we add this in `markdon/__init__.py`

```py3
from .util import ModuleWrap, deprecated

# Also support `version` for backward-compatabillity with <3.0 versions
class _ModuleWrap(ModuleWrap):
    """
    Wrap module with special deprecator to deprecate global objects.

    https://mail.python.org/pipermail/python-ideas/2012-May/014969.html
    """

    @property
    @deprecated("Use '__version__' instead.", stacklevel=3)
    def version(self):
        """Get deprecated version."""

        return super(_ModuleWrap, self).__getattribute__('_module').__version__

    @property
    @deprecated("Use '__version_info__' instead.", stacklevel=3)
    def version_info(self):
        """Get deprecated version info."""

        return super(_ModuleWrap, self).__getattribute__('_module').__version_info__


_ModuleWrap(__name__)
```

So does it work?

```py3
>>> import markdown
>>> import warnings
>>> warnings.simplefilter('always')
>>> markdown.version
__main__:1: DeprecationWarning: 'version' is deprecated. Use '__version__' instead.
'3.0.1'
>>> markdown.__version__
'3.0.1'
```

No problem with other imports either:

```py3
>>> from markdown import extensions
>>> from markdown import *
>>> Markdown().convert('# text')
'<h1>text</h1>'
```

I know that originally the idea was to have the more standard `__version__` etc., but we had to add the old stuff in for compatibility, but this is probably the best idea I can think of to allow a path forward and kill the old.

Had we been including `version` in things in `__all__ = ['Markdown', 'markdown', 'markdownFromFile']`, I'm not sure if it would have worked as well, but since we don't, this is a possible path.

Let me know if this intrigues or horrifies you 🙂 .












Pull request:
Deprecate version and version_info
This issue fixes #739. This essentially implements the closest we can get to PEP562 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 will raised.

Patch:
diff --git a/.spell-dict b/.spell-dict
index c994291..bc3112c 100644
--- a/.spell-dict
+++ b/.spell-dict
@@ -5,6 +5,7 @@ Abrahamsen
 Altmayer
 API
 Artem
+aspell
 Babelmark
 backtick
 backticks
@@ -19,6 +20,7 @@ CamelCase
 Chodarev
 CLI
 CodeHilite
+codehilite
 Cogumbreiro
 convertFile
 CSS
@@ -48,6 +50,7 @@ Kjell
 Krech
 kwargs
 Limberg
+MacOS
 Magne
 MAILTO
 makeExtension
@@ -69,6 +72,7 @@ OSX
 ol
 Ph
 PHP
+Posix
 postprocessor
 postprocessors
 preprocessor
@@ -112,6 +116,7 @@ Swartz
 Szakmeister
 Takteyev
 Tiago
+toc
 tokenized
 tox
 Trac
@@ -126,11 +131,13 @@ untrusted
 UTF
 uTidylib
 versa
+versioning
 Waylan
 whitelist
 WikiLink
 WikiLinks
 Wolever
+workflow
 Xanthakis
 XHTML
 xhtml
@@ -142,3 +149,4 @@ JavaScript
 plugin
 plugins
 configs
+pre
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/docs/contributing.md b/docs/contributing.md
new file mode 100644
index 0000000..c582712
--- /dev/null
+++ b/docs/contributing.md
@@ -0,0 +1,545 @@
+# Contributing to Python-Markdown
+
+The following is a set of guidelines for contributing to Python-Markdown and its
+extensions, which are hosted in the [Python-Markdown Organization] on GitHub.
+These are mostly guidelines, not rules. Use your best judgment, and feel free to
+propose changes to this document in a pull request.
+
+## Code of Conduct
+
+This project and everyone participating in it is governed by the
+[Python-Markdown Code of Conduct]. By participating, you are expected to uphold
+this code. Please report unacceptable behavior to <markdown@freewisdom.org>.
+
+## Project Organization
+
+The core Python-Markdown code base and any built-in extensions are hosted in the
+[Python-Markdown/markdown] project on GitHub. Other extensions maintained by the
+Python-Markdown project may be hosted as separate repositories in the
+[Python-Markdown Organization] on GitHub and must follow best practices for
+third-party extensions.
+
+The [Python-Markdown/markdown] project is organized as follows:
+
+* Branch `master` should generally be stable and release-ready at all times.
+* Version branches should be used for bug-fixes back-ported to the most recent
+  PATCH release.
+* No other branches should be created. Any other branches which exist are
+  preserved for historical reasons only.
+
+## Issues
+
+Feature requests, bug reports, usage questions, and other issues can all be
+raised on the GitHub [issue tracker].
+
+When describing issues try to phrase your ticket in terms of the behavior you
+think needs to change rather than the code you think needs to change.
+
+Make sure you're running the latest version of Python-Markdown before reporting
+an issue.
+
+Search the issue list first for related items. Be sure to check closed issues
+and pull requests. GitHub's search only checks open issues by default.
+
+You may want to check the [syntax rules] and/or [Babelmark] to confirm that your
+expectations align with the rules and/or other implementations of Markdown.
+
+If reporting a syntax bug, you must provide the minimal input which exhibits the
+behavior, the actual output and the output you expected. All three items must be
+provided as textual code blocks (screen-shots are not helpful). It may also be
+helpful to point to the [syntax rules] which specifically address the area of
+concern.
+
+Feature requests will often be closed with a recommendation that they be
+implemented as third party extensions outside of the core Python-Markdown
+library. Keeping new feature requests implemented as third party extensions
+allows us to keep the maintenance overhead of Python-Markdown to a minimum, so
+that the focus can be on continued stability, bug fixes, and documentation.
+
+Closing an issue does not necessarily mean the end of a discussion. If you
+believe your issue has been closed incorrectly, explain why and we'll consider
+if it needs to be reopened.
+
+## Pull Requests
+
+A pull request often represents the start of a discussion, and does not
+necessarily need to be the final, finished submission. In fact, if you discover
+an issue and intend to provide a fix for it, there is no need to open an issue
+first. You can report the issue and provide the fix together in a pull request.
+
+All pull requests should be made from your personal fork of the library hosted
+in your personal GitHub account. Do not create branches on the
+[Python-Markdown/markdown] project for pull requests. All pull requests should
+be implemented in a new branch with a unique name. Remember that if you have an
+outstanding pull request, pushing new commits to the related branch of your
+GitHub repository will also automatically update the pull request. It may help
+to review GitHub's documentation on [Creating a pull request from a fork].
+
+If you are providing a fix for a previously reported issue, you must reference
+the issue in your commit message. Be sure to prefix the reference with one of
+GitHub's [action words] which will automatically close the issue when the pull
+request is merged. For example, `fixes #42` and `closes #42` would be
+acceptable, whereas `ref #42` would not. Of course, if merging a pull request
+should not cause an issue to be closed, then the action word should not be
+included when referencing that issue.
+
+Before being accepted, each pull request must include the applicable code, new
+tests of all new features, updated tests for any changed features, documentation
+updates, and an appropriate update to the release notes. All changes must follow
+the applicable style guides. Failure to meet any one of the requirements is
+likely to delay any serious consideration of your pull request and may even
+cause it to be closed. Of course, if you are in the early stages of development,
+you may include a note in the pull request acknowledging that it is incomplete
+along with a request for feedback.
+
+Pull requests will generally not be accepted if any tests are failing.
+Therefore, it is recommended that you run the tests before submitting your pull
+request. After making a pull request, check the Travis build status in the
+GitHub interface to ensure that all tests are running as expected. If any checks
+fail, you may push additional commits to your branch. GitHub will add those
+commits to the pull request and rerun the checks.
+
+## Style Guides
+
+In an effort to maintain consistency, Python-Markdown adheres to the following
+style guides in its code and documentation. A pull request may be rejected if it
+fails to match the relevant style guides.
+
+### Code Style Guide
+
+Except as noted below, all pull requests should follow Python's standard [PEP8
+Style Guide] and are run through [Flake8] to ensure that the style guide is
+followed.
+
+Legacy code which does not follow the guidelines should only be updated if and
+when other changes (bug fix, feature addition, etc.) are being made to that
+section of code. While new features should be given names that follow modern
+Python naming conventions, existing names should be preserved to avoid backward
+incompatible changes.
+
+Line length is limited to a maximum of 119 characters.
+
+When a line of code does not fit within the line length limit, continuation
+lines should align elements wrapped inside parentheses, brackets and braces
+using a *hanging indent*. When using a hanging indent there should be no
+arguments on the first line and further indentation should be used to clearly
+distinguish itself as a continuation line. The closing parenthesis, bracket or
+brace should be on a line by itself and should line up under the first character
+of the line that starts the multi-line construct.
+
+```python
+my_list = [
+    1, 2, 3,
+    4, 5, 6,
+]
+result = some_function_that_takes_arguments(
+    'a', 'b', 'c',
+    'd', 'e', 'f',
+)
+```
+
+When the conditional part of an `if`-statement is long enough to require that it
+be written across multiple lines, extra indentation should be included on the
+conditional continuation line.
+
+```python
+if (this_is_one_thing
+        and that_is_another_thing):
+    do_something()
+```
+
+### Documentation Style Guide
+
+Documentation should be in American English. The tone of the documentation
+should be simple, plain, objective and well-balanced where possible.
+
+Keep paragraphs reasonably short.
+
+With the exception of code blocks, limit line length to 79 characters. You may
+want to use your editor's tools to automatically hard wrap lines of text.
+
+Don't use abbreviations such as 'e.g.' but instead use the long form, such as
+'For example'.
+
+The documentation is built from the [Markdown] source files in the [`docs`
+directory][docs directory] by the [MkDocs] static site generator. In addition to
+the basic Markdown syntax, the following extensions are supported: [extra],
+[admonition], [smarty], [codehilite], and [toc].
+
+There are a few conventions you should follow when working on the
+documentation.
+
+#### Headers
+
+Headers should use the hash style. For example:
+
+```md
+## Some important topic
+```
+
+The underline style should not be used. Don't do this:
+
+```md
+Some important topic
+====================
+```
+
+#### Links
+
+Links should always use the reference style, with the referenced hyperlinks kept
+at the end of the document.
+
+```md
+Here is a link to [some other thing][other-thing].
+
+More text...
+
+[other-thing]: http://example.com/other/thing
+```
+
+This style helps keep the documentation source consistent and readable.
+
+If you are linking to another document within Python-Markdown's documentation,
+you should use a relative link, and link to the `.md` suffix. If applicable, it
+is preferred that the link includes a hash fragment pointing to the specific
+section of the page. For example:
+
+```md
+[authentication]: reference.md#Markdown
+```
+
+Linking in this style ensures that the links work when browsing the
+documentation on GitHub. If your Markdown editor makes links clickable, they
+will work there as well. When the documentation is built, these links will be
+converted into regular links which point to the built HTML pages.
+
+#### Notes and Warnings
+
+If you want to draw attention to a note or warning, use the syntax defined in
+Python-Markdown's [Admonition Extension]:
+
+```md
+!!! note
+
+    This is the content of the note.
+```
+
+### Commit Message Style Guide
+
+Use the present tense ("Add feature" not "Added feature").
+
+Use the imperative mood ("Move item to..." not "Moves item to...").
+
+Limit the first line to 72 characters or less.
+
+Reference issues and pull requests liberally after the first line. Include a
+summary of the changes/additions made without replicating the content of the
+documentation or release notes. This is where an explanation of the choices made
+should be found. References to issues and pull requests should only provide the
+context in which a choice was made. However, the commit should be able to stand
+on its own.
+
+## Development Environment
+
+To start developing on Python-Markdown is it best to create a [fork] of the
+project on GitHub. After [cloning your fork] to your local system, you will want
+to [configure a remote] that points to the upstream repository so that you can
+[sync changes] made in the original repository with your fork.
+
+It is recommended that all development be done from within a Python [virtual
+environment], which isolates any experimental code from the general system. To
+create a virtual environment, use the following command from the root of the
+local working copy of your GitHub fork:
+
+```sh
+virtualenv venv
+```
+
+That creates a virtual environment which is contained in the `venv` directory
+within your local working copy. Note that the repository is configured so that
+git will ignore any files within a directory named `venv` or `ENV` for this
+very reason.
+
+On Posix systems (Linux, BSD, MacOS, etc.), use the following command to
+activate the environment:
+
+```sh
+source venv/bin/activate
+```
+
+On Windows, use this command instead:
+
+```sh
+venv/Scripts/activate
+```
+
+See the [User Guide] for more information on using virtual environments.
+
+To be able to run the Markdown library directly while working on it, install the
+working copy into the environment in [Development Mode] after activating the
+virtual environment for the first time:
+
+```sh
+pip install --editable .
+```
+
+Now any saved changes will immediately be available within the virtual
+environment.
+
+You can run the command line script with the following command:
+
+```sh
+python -m markdown
+```
+
+And you can directly run the tests with:
+
+```sh
+python -m unittest discover tests
+```
+
+!!! note
+
+    Some tests require the [PyTidyLib] library, which depends on the [HTML Tidy]
+    library. If you do not have PyTidyLib installed, the tests which depend upon
+    it will be skipped. Given the difficulty in installing the HTML Tidy library
+    on many systems, you may choose to leave both libraries uninstalled and
+    depend on the Travis server to run those tests when you submit a pull
+    request.
+
+The above setup will only run tests against the code in one version of Python.
+However,  Python-Markdown supports multiple versions of Python. Therefore, a
+[tox] configuration is included in the repository, which includes test
+environments for all supported Python versions, a [Flake8] test environment, and
+a spellchecker for the documentation. While it is generally fine to leave those
+tests for the Travis server to run when a pull request is submitted, for more
+advanced changes, you may want to run those tests locally. To do so, simply
+install tox:
+
+```sh
+pip install tox
+```
+
+Then, to run all configured test environments, simply call the command `tox`
+with no arguments. See help (`tox -h`) for more options.
+
+!!! note
+
+    The tox environments expect that some dependencies are already installed on
+    your system. For example, by default, any Python version specific
+    environment will fail if that version of Python is not installed.
+    Additionally, the tox environments assume that the [HTML Tidy] library is
+    installed and may fail when attempting to install [PyTidyLib] if it is not.
+    Finally, the `spellchecker` environment requires [aspell] and the
+    `aspell-en` dictionary to be installed. Unfortunately, installing those
+    dependencies may differ significantly from system to system and is outside
+    the scope of this guide.
+
+## Versions
+
+Python-Markdown follows [Semantic Versioning] and uses the
+`MAJOR.MINOR.PATCH[.dev#|a#|b#|rc#]` format for identifying releases. The status
+of the `master` branch should always be identified in the `__version_info__`
+tuple defined in [`markdown/__init__.py`][markdown/__init__.py]. The contents of
+that tuple will automatically be converted into a normalized version which
+conforms to [PEP 440]. An invalid `__version_info__` tuple will raise an error,
+preventing the library from running and the package from building.
+
+### Version Status
+
+A MAJOR version is in development status when the MINOR version is `0`, the
+PATCH version is `0`, and the version includes a `dev` segment.
+
+A MINOR version is in development status when the MINOR version is not `0`, the
+PATCH version is `0`, and the version includes a `dev` segment.
+
+At all other times, the code is considered stable and release-ready.
+
+MAJOR and MINOR releases may or may not get pre-releases (alpha, beta, release
+candidate, etc.) at the discretion of the project maintainers.
+
+### Version Workflow
+
+Bug fixes may be merged from a pull request to the `master` branch at any time
+so long as all tests pass, including one or more new tests which would have
+failed prior to the change.
+
+New features and backward incompatible changes may only be merged to the
+`master` branch when the MAJOR and/or MINOR version is in development status
+pursuant to [Semantic Versioning].
+
+A separate commit to the `master` branch should be made to bump up the MAJOR
+and/or MINOR version and set development status. Only then will any pull
+requests implementing new features or backward incompatible changes be accepted.
+
+If a bug fix is deemed to be important and the `master` branch is in development
+status, a back-port of the fix should be committed to a version branch. If the
+appropriate version branch does not exist, then it should be created and a pull
+request back-porting the fix made against that branch. The version branch should
+be named with the most recently released MINOR version. For example, if the
+`master` branch is at `3.1.dev0` and the most recent MINOR release was `3.0.4`,
+then the version branch would be named `3.0` and any releases from that branch
+would increment the PATCH version only (`3.0.5`, `3.0.6`...).
+
+## Release Process
+
+When a new release is being prepared, the release manager should follow the
+following steps:
+
+1. Verify that all outstanding issues and pull requests related to the release
+   have been resolved.
+
+2. Confirm that the release notes and change log have been updated and indicate
+   the date of the new release.
+
+3. Update the version defined in [`markdown/__init__.py`][markdown/__init__.py].
+
+4. Build a local copy of the documentation, browse through the pages and
+   confirm that no obvious issues exist with the documentation.
+
+5. Create a pull request with a commit message in the following format:
+
+        Bump version to X.X.X
+
+6. After all checks (Travis, etc.) have passed, merge the pull request.
+
+7. Create a git tag with the new version as the tag name and push to the
+   [Python-Markdown/markdown] repository.
+
+8. Deploy the release to [PyPI] with the command `make deploy`.
+
+9. Deploy an update to the documentation using [MkDocs]. The following example
+   assumes that local clones of the [Python-Markdown/markdown] and
+   [Python-Markdown/Python-Markdown.github.io] repositories are in sibling
+   directories named `markdown` and `Python-Markdown.github.io` respectively.
+
+        cd Python-Markdown.github.io
+        mkdocs gh-deploy --config-file ../markdown/mkdocs.yml --remote-branch master
+
+## Issue and Pull Request Labels
+
+Below are the labels used to track and manages issues and pull requests. The
+labels are loosely grouped by their purpose, but it is not necessary for every
+issue to have a label from every group, and an issue may have more than one
+label from the same group.
+
+### Type of Issue or Pull Request
+
+| Label name                   | Description      |
+| ---------------------------- | ---------------- |
+| `bug`{ .label .bug }         | Bug report.      |
+| `feature`{ .label .feature } | Feature request. |
+| `support`{ .label .support } | Support request. |
+
+### Category of Issue or Pull Request
+
+| Label name                       | Description                              |
+| -------------------------------- | ---------------------------------------- |
+| `core`{ .label .core }           | Related to the core parser code.                   |
+| `extension`{ .label .extension } | Related to one or more of the included extensions. |
+| `docs`{ .label .docs }           | Related to the project documentation.              |
+
+### Status of Issue
+
+| Label name                              | Description                       |
+| --------------------------------------- | --------------------------------- |
+| `more-info-needed`{ .label .pending }   | More information needs to be provided.              |
+| `needs-confirmation`{ .label .pending } | The alleged behavior needs to be confirmed.         |
+| `needs-decision`{ .label .pending }     | A decision needs to be made regarding request.      |
+| `confirmed`{ .label .approved }         | Confirmed bug report or approved feature request.   |
+| `someday-maybe`{ .label .low }          | Approved **low priority** request.                  |
+| `duplicate`{ .label .rejected }         | The issue has been previously reported.             |
+| `wontfix`{ .label .rejected }           | The issue will not be fixed for the stated reasons. |
+| `invalid`{ .label .rejected }           | Invalid report (user error, upstream issue, etc).   |
+| `3rd-party`{ .label .rejected }         | Should be implemented as a third party extension.   |
+
+
+### Status of Pull Request
+
+| Label name                            | Description                         |
+| ------------------------------------- | ----------------------------------- |
+| `work-in-progress`{ .label .pending } | A partial solution. More changes will be coming.     |
+| `needs-review`{ .label .pending }     | Needs to be reviewed and/or approved.                |
+| `requires-changes`{ .label .pending } | Awaiting updates after a review.                     |
+| `approved`{ .label .approved }        | The pull request is ready to be merged.              |
+| `rejected`{ .label .rejected }        | The pull request is rejected for the stated reasons. |
+
+[Python-Markdown Organization]: https://github.com/Python-Markdown
+[Python-Markdown Code of Conduct]: https://github.com/Python-Markdown/markdown/blob/master/CODE_OF_CONDUCT.md
+[Python-Markdown/markdown]: https://github.com/Python-Markdown/markdown
+[issue tracker]: https://github.com/Python-Markdown/markdown/issues
+[syntax rules]: http://daringfireball.net/projects/markdown/syntax
+[Babelmark]: http://johnmacfarlane.net/babelmark2/
+[Creating a pull request from a fork]: https://help.github.com/articles/creating-a-pull-request-from-a-fork/
+[action words]: https://help.github.com/articles/closing-issues-using-keywords/
+[PEP8 Style Guide]: https://www.python.org/dev/peps/pep-0008/
+[Flake8]: http://flake8.pycqa.org/en/latest/index.html
+[Markdown]: https://daringfireball.net/projects/markdown/basics
+[docs directory]: https://github.com/Python-Markdown/markdown/tree/master/docs
+[MkDocs]: https://www.mkdocs.org/
+[extra]: extensions/extra.md
+[admonition]: extensions/admonition.md
+[smarty]: extensions/smarty.md
+[codehilite]: extensions/code_hilite.md
+[toc]: extensions/toc.md
+[Admonition Extension]: extensions/admonition.md#syntax
+[fork]: https://help.github.com/articles/about-forks
+[cloning your fork]: https://help.github.com/articles/cloning-a-repository/
+[configure a remote]: https://help.github.com/articles/configuring-a-remote-for-a-fork
+[sync changes]: https://help.github.com/articles/syncing-a-fork
+[virtual environment]: https://virtualenv.pypa.io/en/stable/
+[User Guide]: https://virtualenv.pypa.io/en/stable/userguide/#usage
+[Development Mode]: https://setuptools.readthedocs.io/en/latest/setuptools.html#development-mode
+[PyTidyLib]: http://countergram.github.io/pytidylib/
+[HTML Tidy]: http://www.html-tidy.org/
+[tox]: https://tox.readthedocs.io/en/latest/
+[aspell]: http://aspell.net/
+[Semantic Versioning]: https://semver.org/
+[markdown/__init__.py]: https://github.com/Python-Markdown/markdown/blob/master/markdown/__init__.py#L43
+[PEP 440]: https://www.python.org/dev/peps/pep-0440/
+[PyPI]: https://pypi.org/project/Markdown/
+[Python-Markdown/Python-Markdown.github.io]: https://github.com/Python-Markdown/Python-Markdown.github.io
+
+<style type="text/css">
+    /* GitHub Label Styles */
+
+    code.label {
+        color: #000000;
+        font-weight: 600;
+        line-height: 15px;
+        display: inline-block;
+        padding: 4px 6px;
+    }
+    code.bug {
+        background-color: #c45b46;
+    }
+    code.feature {
+        background-color: #7b17d8;
+        color: #ffffff;
+    }
+    code.support {
+        background-color: #efbe62;
+    }
+    code.core {
+        background-color: #0b02e1;
+        color: #ffffff;
+    }
+    code.extension {
+        background-color: #709ad8;
+    }
+    code.docs {
+        background-color: #b2ffeb;
+    }
+    code.approved {
+        background-color: #beed6d;
+    }
+    code.low {
+        background-color: #dddddd;
+    }
+    code.pending {
+        background-color: #f0f49a;
+    }
+    code.rejected {
+        background-color: #f7c7be;
+    }
+</style>
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/mkdocs.yml b/mkdocs.yml
index 40f7731..e01021e 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -38,6 +38,7 @@ nav:
       - WikiLinks: extensions/wikilinks.md
   - Extension API: extensions/api.md
   - Test Tools: test_tools.md
+  - Contributing to Python-Markdown: contributing.md
   - Change Log: change_log/index.md
   - Release Notes for v.3.0: change_log/release-3.0.md
   - Release Notes for v.2.6: change_log/release-2.6.md


Test patch:
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)