Skip to content

Commit

Permalink
Refactored extension importing.
Browse files Browse the repository at this point in the history
We now use importlib which means we no longer support Python 2.6.
Also, this refactor properly imports third party extensions which reside
at the root of PYTHONPATH. Previously, either `markdown.extensions.` or
`mdx_` would be appended to any extension name that did not contain a
dot, which required third party extensions to either be in submodules or
use the old `mdx_` naming convention.

This commit is also in preperation for Python-Markdown#336. It will now be much easier to
deprecate (and later remove) support for the old ways of handling extension
names.
  • Loading branch information
waylan committed Aug 27, 2014
1 parent 8c29487 commit 44e718e
Show file tree
Hide file tree
Showing 6 changed files with 45 additions and 27 deletions.
1 change: 0 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
language: python
env:
- TOXENV=py26
- TOXENV=py27
- TOXENV=py32
- TOXENV=py33
Expand Down
5 changes: 4 additions & 1 deletion docs/release-2.5.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@ Python-Markdown 2.5 Release Notes
We are pleased to release Python-Markdown 2.5 which adds a few new features
and fixes various bugs. See the list of changes below for details.

Python-Markdown supports Python versions 2.6, 2.7, 3.2, 3.3, and 3.4.
Python-Markdown supports Python versions 2.7, 3.2, 3.3, and 3.4.

Backwards-incompatible Changes
------------------------------

* Python-Markdown no longer supports Python version 2.6. You must be using Python 2.7+
or Python-Markdown 2.5 will not load.

* The `force_linenos` config key on the [CodeHilite Extension] has been deprecated
and will raise a `KeyError` if provided. In the previous release (2.4), it was issuing
a `DeprecationWarning`. The [`linenums`][linenums] keyword should be used instead,
Expand Down
38 changes: 23 additions & 15 deletions markdown/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import codecs
import sys
import logging
import importlib
from . import util
from .preprocessors import build_preprocessors
from .blockprocessors import build_block_parser
Expand Down Expand Up @@ -163,6 +164,8 @@ def registerExtensions(self, extensions, configs):
ext = self.build_extension(ext, configs.get(ext, []))
if isinstance(ext, Extension):
ext.extendMarkdown(self, globals())
logger.info('Successfully loaded extension "%s.%s".'
% (ext.__class__.__module__, ext.__class__.__name__))
elif ext is not None:
raise TypeError(
'Extension "%s.%s" must be of type: "markdown.Extension"'
Expand All @@ -187,23 +190,28 @@ def build_extension(self, ext_name, configs = []):
pairs = [x.split("=") for x in ext_args.split(",")]
configs.update([(x.strip(), y.strip()) for (x, y) in pairs])

# Setup the module name
module_name = ext_name
if '.' not in ext_name:
module_name = '.'.join(['markdown.extensions', ext_name])

# Try loading the extension first from one place, then another
try: # New style (markdown.extensions.<extension>)
module = __import__(module_name, {}, {}, [str(module_name.rpartition('.')[0])])
try:
# Assume string uses dot syntax (`path.to.some.module`)
module = importlib.import_module(ext_name)
logger.debug('Successfuly imported extension module "%s".' % ext_name)
except ImportError:
module_name_old_style = '_'.join(['mdx', ext_name])
try: # Old style (mdx_<extension>)
module = __import__(module_name_old_style)
except ImportError as e:
message = "Failed loading extension '%s' from '%s' or '%s'" \
% (ext_name, module_name, module_name_old_style)
e.args = (message,) + e.args[1:]
raise
# Preppend `markdown.extensions.` to name
module_name = '.'.join(['markdown.extensions', ext_name])
try:
module = importlib.import_module(module_name)
logger.debug('Successfuly imported extension module "%s".' % module_name)
except ImportError:
# Preppend `mdx_` to name
module_name_old_style = '_'.join(['mdx', ext_name])
try:
module = importlib.import_module(module_name_old_style)
logger.debug('Successfuly imported extension module "%s".' % module_name_old_style)
except ImportError as e:
message = "Failed loading extension '%s' from '%s', '%s' or '%s'" \
% (ext_name, ext_name, module_name, module_name_old_style)
e.args = (message,) + e.args[1:]
raise

# If the module is loaded successfully, we expect it to define a
# function called makeExtension()
Expand Down
1 change: 0 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,6 @@ def has_docs(self):
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.2',
Expand Down
25 changes: 17 additions & 8 deletions tests/test_apis.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,24 +276,33 @@ def testLoadExtensionFailure(self):

def testLoadBadExtension(self):
""" Test loading of an Extension with no makeExtension function. """
_create_fake_extension(name='fake', has_factory_func=False)
self.assertRaises(AttributeError, markdown.Markdown, extensions=['fake'])
_create_fake_extension(name='fake_a', has_factory_func=False)
self.assertRaises(AttributeError, markdown.Markdown, extensions=['fake_a'])

def testNonExtension(self):
""" Test loading a non Extension object as an extension. """
_create_fake_extension(name='fake', is_wrong_type=True)
self.assertRaises(TypeError, markdown.Markdown, extensions=['fake'])
_create_fake_extension(name='fake_b', is_wrong_type=True)
self.assertRaises(TypeError, markdown.Markdown, extensions=['fake_b'])

def testBaseExtention(self):
""" Test that the base Extension class will raise NotImplemented. """
_create_fake_extension(name='fake')
_create_fake_extension(name='fake_c')
self.assertRaises(NotImplementedError,
markdown.Markdown, extensions=['fake'])
markdown.Markdown, extensions=['fake_c'])

def testDotSyntaxExtention(self):
""" Test that dot syntax imports properly (not using mdx_). """
_create_fake_extension(name='fake_d', use_old_style=False)
self.assertRaises(NotImplementedError,
markdown.Markdown, extensions=['fake_d'])


def _create_fake_extension(name, has_factory_func=True, is_wrong_type=False):
def _create_fake_extension(name, has_factory_func=True, is_wrong_type=False, use_old_style=True):
""" Create a fake extension module for testing. """
mod_name = '_'.join(['mdx', name])
if use_old_style:
mod_name = '_'.join(['mdx', name])
else:
mod_name = name
if not PY3:
# mod_name must be bytes in Python 2.x
mod_name = bytes(mod_name)
Expand Down
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[tox]
envlist = py26, py27, py31, py32, py33, py34
envlist = py27, py31, py32, py33, py34

[testenv]
downloadcache = {toxworkdir}/cache
Expand Down

0 comments on commit 44e718e

Please sign in to comment.