aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.travis.yml1
-rw-r--r--docs/release-2.5.txt91
-rw-r--r--markdown/__init__.py60
-rwxr-xr-xsetup.py1
-rw-r--r--tests/test_apis.py42
-rw-r--r--tox.ini2
6 files changed, 138 insertions, 59 deletions
diff --git a/.travis.yml b/.travis.yml
index 1931a20..e912c73 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,6 +1,5 @@
language: python
env:
- - TOXENV=py26
- TOXENV=py27
- TOXENV=py32
- TOXENV=py33
diff --git a/docs/release-2.5.txt b/docs/release-2.5.txt
index 0d895bb..a81acfd 100644
--- a/docs/release-2.5.txt
+++ b/docs/release-2.5.txt
@@ -10,15 +10,23 @@ 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 version 2.5 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
+ versions 2.7, 3.2, 3.3, or 3.4. While Python-Markdown is no longer tested against
+ Python 2.6, you may be able to get it working if you install a copy of [importlib]
+ which has been backported for Python 2.6. However, the developers of Python-Markdown
+ offer no guarentees in that situation.
+
+[importlib]: https://pypi.python.org/pypi/importlib
+
* 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,
-which provides more control of the output.
+ 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, which provides more control of the output.
[CodeHilite Extension]: extensions/code_hilite.html
[linenumes]: extensions/code_hilite.html#usage
@@ -26,36 +34,73 @@ which provides more control of the output.
What's New in Python-Markdown 2.5
---------------------------------
-* The [Smarty Extension] has had a number of additional configuration settings
-added, which allows one to define their own subtitutions to better support
-languages other than English. Thanks to [Martin Altmayer] for implementing this feature.
+* The [Smarty Extension] has had a number of additional configuration settings
+ added, which allows one to define their own subtitutions to better support
+ languages other than English. Thanks to [Martin Altmayer] for implementing this
+ feature.
[Smarty Extension]: extensions/smarty.html
[Martin Altmayer]:https://github.com/MartinAltmayer
-* The Extension Configuration code has been refactord to make it a little easier
-for extension authors to work with config settings. As a result, the
-[`extension_configs`][ec] keyword now accepts a dictionary rather than requiring
-a list of tuples. A list of tuples is still supported so no one needs to change
-their existing code. This should also simplify the learning curve for new users.
+* Named Extensions (strings passed to the [`extensions`][ex] keyword of
+ `markdown.Markdown`) can now point to any module and/or Class on your PYTHONPATH.
+ While dot notation was previously supported, a module could not be at the root of
+ your PYTHONPATH. The name had to contain at least one dot (requiring it to be a
+ submodule). This restriction no longer exists.
+
+ Additionaly, a Class may be specified in the name. The class must be at the end of
+ the name (which uses dot notation from PYTHONPATH) and be seperated by a colon from
+ the module.
+
+ Therefore, if you were to import the class like this:
+
+ from path.to.module import SomeExtensionClass
+
+ Then the named extension would comprise this string:
+
+ "path.to.module:SomeExtensionClass"
+
+ This allows multiple extensions to be implemented within the same module and still
+ accessable when the user isn't able to import the extension directly (perhaps from
+ a template filter or the command line).
+
+ This also means that extension modules are no longer required to include the
+ `makeExtension` function which returns an instance of the extension class. However,
+ if the user does not specify the class name (she only provides `"path.to.module"`)
+ the extension will fail to load without the `makeExtension` function included in
+ the module. Extension authors will want to document carfully what is required to
+ load their extensions.
+
+[ex]: reference.html#extensions
+
+* The Extension Configuration code has been refactord to make it a little easier
+ for extension authors to work with config settings. As a result, the
+ [`extension_configs`][ec] keyword now accepts a dictionary rather than requiring
+ a list of tuples. A list of tuples is still supported so no one needs to change
+ their existing code. This should also simplify the learning curve for new users.
+
+ Extension authors are encouraged to review the new methods available on the
+ `markdown.extnesions.Extension` class for handling configs and adjust their
+ code going forward. The included extensions provide a model for best practices.
[ec]: reference.html#extension_configs
-* The [Command Line Interface][cli] now accepts a `--extensions_config` (or `-c`) option
-which accepts a filename and passes the parsed content of a [YAML] or [JSON] file to the
-[`extension_configs`][ec] keyword of the `markdown.Markdown` class. The contents of
-the YAML or JSON must map to a Python Dictionary which matches the format required
-by the `extension_configs` kerword. Note that [PyYAML] is required to parse YAML files.
+* The [Command Line Interface][cli] now accepts a `--extensions_config` (or `-c`)
+ option which accepts a filename and passes the parsed content of a [YAML] or
+ [JSON] file to the [`extension_configs`][ec] keyword of the `markdown.Markdown`
+ class. The contents of the YAML or JSON must map to a Python Dictionary which
+ matches the format required by the `extension_configs` kerword. Note that
+ [PyYAML] is required to parse YAML files.
[cli]: cli.html#using-extensions
[YAML]: http://yaml.org/
[JSON]: http://json.org/
[PyYAML]: http://pyyaml.org/
-* There have been various refactors of the testing framework. While those changes
-will not directly effect end users, the code is being better tested which will
-benefit everyone.
+* There have been various refactors of the testing framework. While those changes
+ will not directly effect end users, the code is being better tested which will
+ benefit everyone.
-* Various bug fixes have been made. See the
-[commit log](https://github.com/waylan/Python-Markdown/commits/master)
-for a complete history of the changes.
+* Various bug fixes have been made. See the
+ [commit log](https://github.com/waylan/Python-Markdown/commits/master)
+ for a complete history of the changes.
diff --git a/markdown/__init__.py b/markdown/__init__.py
index 59dda4c..28f30c8 100644
--- a/markdown/__init__.py
+++ b/markdown/__init__.py
@@ -36,6 +36,7 @@ from .__version__ import version, version_info
import codecs
import sys
import logging
+import importlib
from . import util
from .preprocessors import build_preprocessors
from .blockprocessors import build_block_parser
@@ -163,6 +164,8 @@ class Markdown(object):
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"'
@@ -187,35 +190,46 @@ class Markdown(object):
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])
+ # Get class name (if provided): `path.to.module:ClassName`
+ ext_name, class_name = ext_name.split(':', 1) if ':' in ext_name else (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)
+ # 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 class_name:
+ # Load given class name from module.
+ return getattr(module, class_name)(configs.items())
+ else:
+ # Expect makeExtension() function to return a class.
+ try:
+ return module.makeExtension(configs.items())
+ except AttributeError as e:
+ message = e.args[0]
+ message = "Failed to initiate extension " \
+ "'%s': %s" % (ext_name, message)
e.args = (message,) + e.args[1:]
raise
- # If the module is loaded successfully, we expect it to define a
- # function called makeExtension()
- try:
- return module.makeExtension(configs.items())
- except AttributeError as e:
- message = e.args[0]
- message = "Failed to initiate extension " \
- "'%s': %s" % (ext_name, message)
- e.args = (message,) + e.args[1:]
- raise
-
def registerExtension(self, extension):
""" This gets called by the extension """
self.registeredExtensions.append(extension)
diff --git a/setup.py b/setup.py
index 568e8c5..f635b48 100755
--- a/setup.py
+++ b/setup.py
@@ -235,7 +235,6 @@ setup(
'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',
diff --git a/tests/test_apis.py b/tests/test_apis.py
index a5e0d73..e347cf9 100644
--- a/tests/test_apis.py
+++ b/tests/test_apis.py
@@ -40,6 +40,19 @@ class TestMarkdownBasics(unittest.TestCase):
""" Test simple input. """
self.assertEqual(self.md.convert('foo'), '<p>foo</p>')
+ def testInstanceExtension(self):
+ """ Test Extension loading with a class instance. """
+ from markdown.extensions.footnotes import FootnoteExtension
+ markdown.Markdown(extensions=[FootnoteExtension()])
+
+ def testNamedExtension(self):
+ """ Test Extension loading with Name (`path.to.module`). """
+ markdown.Markdown(extensions=['markdown.extensions.footnotes'])
+
+ def TestNamedExtensionWithClass(self):
+ """ Test Extension loading with class name (`path.to.module:Class`). """
+ markdown.Markdown(extensions=['markdown.extensions.footnotes:FootnoteExtension'])
+
class TestBlockParser(unittest.TestCase):
""" Tests of the BlockParser class. """
@@ -276,24 +289,33 @@ class TestErrors(unittest.TestCase):
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_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'])
+ 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)
@@ -563,7 +585,7 @@ class TestCliOptionParsing(unittest.TestCase):
'end_url': '.html',
'html_class': 'test',
},
- 'footnotes': {
+ 'footnotes:FootnotesExtension': {
'PLACE_MARKER': '~~~footnotes~~~'
}
}
@@ -579,7 +601,7 @@ class TestCliOptionParsing(unittest.TestCase):
'end_url': '.html',
'html_class': 'test',
},
- 'footnotes': {
+ 'footnotes:FootnotesExtension': {
'PLACE_MARKER': '~~~footnotes~~~'
}
}
diff --git a/tox.ini b/tox.ini
index 7968fba..5ccefce 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
[tox]
-envlist = py26, py27, py31, py32, py33, py34
+envlist = py27, py31, py32, py33, py34
[testenv]
downloadcache = {toxworkdir}/cache