From 9a5b11116e2ea8a240fa2d03cad9de334023da9d Mon Sep 17 00:00:00 2001 From: Waylan Limberg Date: Fri, 29 Aug 2014 20:00:39 -0400 Subject: Mark special treatment of extension names as PendingDeprecation The builtin extensions will no longer get special treatment and have the path ("markdown.extensions.") appended . The same applies for "mdx_" extensions. All names extension must provide the full path. Fixes #336. Also deprecating support for passing in extension config settings as part of the string name. The extension_configs keyword should be used instead. Fixes #335. Also raising PendingDeprecationWarnings for positional args or the "config" keyword on the Extension Class. Pass each setting as a seperate keyword instead. Docs and tests are updated. Still need to update extension API docs. --- docs/cli.txt | 13 +++++-------- docs/reference.txt | 42 ++++++++++++++++++++++------------------- docs/release-2.5.txt | 27 ++++++++++++++++++++++++++ markdown/__init__.py | 33 ++++++++++++++++++++++++++------ markdown/extensions/__init__.py | 15 ++++++++++++++- tests/test_apis.py | 20 +++++++++++++++----- tests/test_extensions.py | 16 ---------------- 7 files changed, 111 insertions(+), 55 deletions(-) diff --git a/docs/cli.txt b/docs/cli.txt index acad4ff..7741d34 100644 --- a/docs/cli.txt +++ b/docs/cli.txt @@ -147,17 +147,14 @@ The `--extension_configs` option will only support YAML config files if [PyYaml] installed on your system. JSON should work with no additional dependencies. The format of your config file is automatically detected. -As an alternative, you may append the extension configs as a string to the extension name -that is provided to the `-x-` option in the following format: - - $ python -m markdown -x "markdown.extensions.footnotes(PLACE_MARKER=~~~~~~~~,UNIQUE_IDS=1)" input.txt - -Note that there are no quotes or whitespace in the above format, which severely limits -how it can be used. For more complex settings, it is suggested that the -`--extension_configs` option be used. +!!!warning + The previously documented method of appending the extension configs as a string to the + extension name will be deprecated in Python-Markdown version 2.6. The `--extension_configs` + option should be used instead. See the [2.5 release notes] for more information. [ec]: reference.html#extension_configs [YAML]: http://yaml.org/ [JSON]: http://json.org/ [PyYAML]: http://pyyaml.org/ +[2.5 release notes]: release-2.5.txt diff --git a/docs/reference.txt b/docs/reference.txt index 033b658..afc3b05 100644 --- a/docs/reference.txt +++ b/docs/reference.txt @@ -66,11 +66,12 @@ The following options are available on the `markdown.markdown` function: The list of extensions may contain instances of extensions and/or strings of extension names. - extensions=[MyExtension(), 'path.to.my.ext', 'extra'] + extensions=[MyExtension(), 'path.to.my.ext'] !!! warning - The prefered method is to pass in an instance of an extension. The other - methods may be removed in a future version of Python-Markdown. + The prefered method is to pass in an instance of an extension. Strings + should only be used when it is impossable to import the Extension Class + directly (from the command line or in a template). When passing in extension instances, each class instance must be a subclass of `markdown.extensions.Extension` and any configuration options should be @@ -86,24 +87,27 @@ The following options are available on the `markdown.markdown` function: If an extension name is provided as a string, the extension must be importable as a python module on your PYTHONPATH. Python's dot notation is supported. Therefore, to import the 'extra' extension, one could do - `extensions=['markdown.extensions.extra']` However, if no dots are provided - in the string (`extensions=['extra']`) Markdown will first look for the - module `markdown.extensions.extra` (the built-in extension), then a module - named `mdx_extra` ('mdx_' will be appended to the beginning of the string) - at the root of your PYTHONPATH. + `extensions=['markdown.extensions.extra']` - When loading an extension by name (as a string), you may either pass in - configuration settings to the extension using the - [extension_configs](#extension_configs) keyword or by appending the - settings to the name in the following format: + Additionaly, a Class may be specified in the name. The class must be at the end of + the name and be seperated by a colon from the module. - extensions=['name(option1=value,option2=value)'] - - Note that there are no quotes or whitespace in the above format, which - severely limits how it can be used. For more complex settings, it is - suggested that an instance of a class be passed in or, if that's not - possible, the [extension_configs](#extension_configs) keyword - be used. + 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" + + !!! note + You should only need to specify the class name if more than one extension is defined + within the same module. The extensions that come with Python-Markdown do *not* + need to have the class name specified. + + When loading an extension by name (as a string), you may pass in + configuration settings to the extension using the + [extension_configs](#extension_configs) keyword. !!! seealso "See Also" See the documentation of the [Extension API](extensions/api.html) for diff --git a/docs/release-2.5.txt b/docs/release-2.5.txt index a81acfd..d45fe24 100644 --- a/docs/release-2.5.txt +++ b/docs/release-2.5.txt @@ -31,6 +31,33 @@ Backwards-incompatible Changes [CodeHilite Extension]: extensions/code_hilite.html [linenumes]: extensions/code_hilite.html#usage +* In previous versions of Python-Markdown, the builtin extensions received + special status and did not require the full path to be provided. Additionaly, + third party extensions whose name started with "mdx_" received the same + special treatment. This behavior will be deprecated in version 2.6 and will + raise a PendingDeprecationWarning in 2.5. Ensure that you always use the full + path to your extensions. For example, if you previously did the following: + + markdown.markdown(text, extensions=['extra']) + + You should change your code to the following: + + markdown.markdown(text, extensions=['markdown.extensions.extra']) + + The same applies to the command line: + + $ python -m markdown -x markdown.extensions.extra input.txt + + See the [documentation](reference.html#extensions) for a full explaination + of the current behavior. + +* The previously documented method of appending the extension configs as + a string to the extension name will be deprecated in Python-Markdown + version 2.6 and will raise a PendingDeprecationWarning in 2.5. The + [extension_configs](reference.html#extension_configs) keyword should + be used instead. See the [documentation](reference.html#extension-configs) + for a full explaination of the current behavior. + What's New in Python-Markdown 2.5 --------------------------------- diff --git a/markdown/__init__.py b/markdown/__init__.py index 0fdcd97..6bf84d0 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 warnings import importlib from . import util from .preprocessors import build_preprocessors @@ -161,7 +162,7 @@ class Markdown(object): """ for ext in extensions: if isinstance(ext, util.string_type): - ext = self.build_extension(ext, configs.get(ext, [])) + ext = self.build_extension(ext, configs.get(ext, {})) if isinstance(ext, Extension): ext.extendMarkdown(self, globals()) logger.info('Successfully loaded extension "%s.%s".' @@ -173,22 +174,29 @@ class Markdown(object): return self - def build_extension(self, ext_name, configs = []): + def build_extension(self, ext_name, configs): """Build extension by name, then return the module. The extension name may contain arguments as part of the string in the following format: "extname(key1=value1,key2=value2)" """ - - # Parse extensions config params (ignore the order) + configs = dict(configs) + + # Parse extensions config params (ignore the order) pos = ext_name.find("(") # find the first "(" if pos > 0: ext_args = ext_name[pos+1:-1] ext_name = ext_name[:pos] pairs = [x.split("=") for x in ext_args.split(",")] configs.update([(x.strip(), y.strip()) for (x, y) in pairs]) + warnings.warn('Setting configs in the Named Extension string is pending deprecation. ' + 'It is recommended that you pass an instance of the extension class to ' + 'Markdown or use the "extension_configs" keyword. The current behavior ' + 'will be deprecated in version 2.6 and raise an error in version 2.7. ' + 'See the Release Notes for Python-Markdown version 2.5 for more info.', + PendingDeprecationWarning) # Get class name (if provided): `path.to.module:ClassName` ext_name, class_name = ext_name.split(':', 1) if ':' in ext_name else (ext_name, '') @@ -204,12 +212,25 @@ class Markdown(object): try: module = importlib.import_module(module_name) logger.debug('Successfuly imported extension module "%s".' % module_name) + warnings.warn('Using short names for Markdown\'s builtin extensions is pending deprecation. ' + 'Use the full path to the extension with Python\'s dot notation ' + '(eg: "%s" instead of "%s"). The current behavior will be deprecated in ' + 'version 2.6 and raise an error in version 2.7. See the Release Notes for ' + 'Python-Markdown version 2.5 for more info.' % (module_name, ext_name), + PendingDeprecationWarning) 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) + warnings.warn('Markdown\'s behavuor of appending "mdx_" to an extension name ' + 'is pending deprecation. Use the full path to the extension with ' + 'Python\'s dot notation (eg: "%s" instead of "%s"). The ' + 'current behavior will be deprecated in version 2.6 and raise an ' + 'error in version 2.7. See the Release Notes for Python-Markdown ' + 'version 2.5 for more info.' % (module_name_old_style, ext_name), + PendingDeprecationWarning) except ImportError as e: message = "Failed loading extension '%s' from '%s', '%s' or '%s'" \ % (ext_name, ext_name, module_name, module_name_old_style) @@ -218,11 +239,11 @@ class Markdown(object): if class_name: # Load given class name from module. - return getattr(module, class_name)(configs=configs) + return getattr(module, class_name)(**configs) else: # Expect makeExtension() function to return a class. try: - return module.makeExtension(configs=configs) + return module.makeExtension(**configs) except AttributeError as e: message = e.args[0] message = "Failed to initiate extension " \ diff --git a/markdown/extensions/__init__.py b/markdown/extensions/__init__.py index 2751eef..03b2a4c 100644 --- a/markdown/extensions/__init__.py +++ b/markdown/extensions/__init__.py @@ -5,6 +5,7 @@ Extensions from __future__ import unicode_literals from ..util import parseBoolValue +import warnings class Extension(object): """ Base class for extensions to subclass. """ @@ -25,8 +26,20 @@ class Extension(object): # (there only ever used to be one so we use arg[0]) if len(args): self.setConfigs(args[0]) + warnings.warn('Extension classes accepting positional args is pending Deprecation. ' + 'Each setting should be passed into the Class as a keyword. Positional ' + 'args will be deprecated in version 2.6 and raise an error in version ' + '2.7. See the Release Notes for Python-Markdown version 2.5 for more info.', + PendingDeprecationWarning) # check for configs kwarg for backward compat. - self.setConfigs(kwargs.pop('configs', {})) + if 'configs' in kwargs.keys(): + self.setConfigs(kwargs.pop('configs', {})) + warnings.warn('Extension classes accepting a dict on the single keyword "config" is ' + 'pending Deprecation. Each setting should be passed into the Class as ' + 'a keyword directly. The "config" keyword will be deprecated in version ' + '2.6 and raise an error in version 2.7. See the Release Notes for ' + 'Python-Markdown version 2.5 for more info.', + PendingDeprecationWarning) # finally, use kwargs self.setConfigs(kwargs) diff --git a/tests/test_apis.py b/tests/test_apis.py index 010bf70..b19db62 100644 --- a/tests/test_apis.py +++ b/tests/test_apis.py @@ -303,14 +303,24 @@ class TestErrors(unittest.TestCase): 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, + def testMdxExtention(self): + """ Test that appending mdx_ raises a PendingDeprecationWarning. """ + _create_fake_extension(name='fake_d', use_old_style=True) + self.assertRaises(PendingDeprecationWarning, markdown.Markdown, extensions=['fake_d']) + def testShortNameExtention(self): + """ Test that using a short name raises a PendingDeprecationWarning. """ + self.assertRaises(PendingDeprecationWarning, + markdown.Markdown, extensions=['footnotes']) + + def testStringConfigExtention(self): + """ Test that passing configs to an Extension in the name raises a PendingDeprecationWarning. """ + self.assertRaises(PendingDeprecationWarning, + markdown.Markdown, extensions=['markdown.extension.footnotes(PLACE_MARKER=FOO)']) + -def _create_fake_extension(name, has_factory_func=True, is_wrong_type=False, use_old_style=True): +def _create_fake_extension(name, has_factory_func=True, is_wrong_type=False, use_old_style=False): """ Create a fake extension module for testing. """ if use_old_style: mod_name = '_'.join(['mdx', name]) diff --git a/tests/test_extensions.py b/tests/test_extensions.py index a7be627..a21fd94 100644 --- a/tests/test_extensions.py +++ b/tests/test_extensions.py @@ -47,22 +47,6 @@ class TestExtensionClass(unittest.TestCase): # self.ext.setConfig('bad', 'baz) ==> KeyError self.assertRaises(KeyError, self.ext.setConfig, 'bad', 'baz') - def testConfigAsArgListOnInit(self): - ext = self.ExtKlass([('foo', 'baz'), ('bar', 'blah')]) - self.assertEqual(ext.getConfigs(), {'foo': 'baz', 'bar': 'blah'}) - - def testConfigAsArgDictOnInit(self): - ext = self.ExtKlass({'foo': 'baz', 'bar': 'blah', 'bar': 'blah'}) - self.assertEqual(ext.getConfigs(), {'foo': 'baz', 'bar': 'blah'}) - - def testConfigAsKwargListOnInit(self): - ext = self.ExtKlass(configs=[('foo', 'baz'), ('bar', 'blah')]) - self.assertEqual(ext.getConfigs(), {'foo': 'baz', 'bar': 'blah'}) - - def testConfigAsKwargDictOnInit(self): - ext = self.ExtKlass(configs={'foo': 'baz', 'bar': 'blah'}) - self.assertEqual(ext.getConfigs(), {'foo': 'baz', 'bar': 'blah'}) - def testConfigAsKwargsOnInit(self): ext = self.ExtKlass(foo='baz', bar='blah') self.assertEqual(ext.getConfigs(), {'foo': 'baz', 'bar': 'blah'}) -- cgit v1.2.3