diff --git a/PKG-INFO b/PKG-INFO index f3e4cabb95c8f079b9c335b8b33f78a3a2517a0d..f96a4c0c19866892c7f608dac72a119f5d827d0d 100644 --- a/PKG-INFO +++ b/PKG-INFO @@ -1,7 +1,7 @@ Metadata-Version: 2.1 Name: dict2xml -Version: 1.6.1 -Summary: small script to output xml as a string from a python dictionary +Version: 1.7.0 +Summary: Small utility to convert a python dictionary into an XML string Home-page: http://github.com/delfick/python-dict2xml Author: Stephen Moore Author-email: stephen@delfick.com @@ -32,20 +32,21 @@ Description: dict2xml .. code-block:: python - from dict2xml import dict2xml as xmlify + from dict2xml import dict2xml + data = { - 'a' : 1 - , 'b' : [2, 3] - , 'c' : { - 'd' : [ - {'p' : 9} - , {'o' : 10} - ] - , 'e': 7 - } + 'a': 1, + 'b': [2, 3], + 'c': { + 'd': [ + {'p': 9}, + {'o': 10} + ], + 'e': 7 } + } - print xmlify(data, wrap="all", indent=" ") + print dict2xml(data, wrap="all", indent=" ") Output ------ @@ -97,10 +98,16 @@ Description: dict2xml Changelog --------- - 1.6.1 + 1.7.0 - 16 April, 2020 + * Use collections.abc to avoid deprecation warning. Thanks @mangin. + * This library no longer supports Python2 and is only supported for + Python3.6+. Note that the library should still work in Python3.5 as I + have not used f-strings, but the framework I use for the tests is only 3.6+. + + 1.6.1 - August 27, 2019 * Include readme and LICENSE in the package - 1.6 + 1.6 - April 27, 2018 * No code changes * changed the licence to MIT * Added more metadata to pypi diff --git a/README.rst b/README.rst index 7bda705c05775354848833f87c4939e7f21ea03e..a3b645131026854f09983d9d59a3c2485ccc82c4 100644 --- a/README.rst +++ b/README.rst @@ -24,20 +24,21 @@ example .. code-block:: python - from dict2xml import dict2xml as xmlify + from dict2xml import dict2xml + data = { - 'a' : 1 - , 'b' : [2, 3] - , 'c' : { - 'd' : [ - {'p' : 9} - , {'o' : 10} - ] - , 'e': 7 - } + 'a': 1, + 'b': [2, 3], + 'c': { + 'd': [ + {'p': 9}, + {'o': 10} + ], + 'e': 7 } + } - print xmlify(data, wrap="all", indent=" ") + print dict2xml(data, wrap="all", indent=" ") Output ------ @@ -89,10 +90,16 @@ Limitations Changelog --------- -1.6.1 +1.7.0 - 16 April, 2020 + * Use collections.abc to avoid deprecation warning. Thanks @mangin. + * This library no longer supports Python2 and is only supported for + Python3.6+. Note that the library should still work in Python3.5 as I + have not used f-strings, but the framework I use for the tests is only 3.6+. + +1.6.1 - August 27, 2019 * Include readme and LICENSE in the package -1.6 +1.6 - April 27, 2018 * No code changes * changed the licence to MIT * Added more metadata to pypi diff --git a/dict2xml.egg-info/PKG-INFO b/dict2xml.egg-info/PKG-INFO index f3e4cabb95c8f079b9c335b8b33f78a3a2517a0d..f96a4c0c19866892c7f608dac72a119f5d827d0d 100644 --- a/dict2xml.egg-info/PKG-INFO +++ b/dict2xml.egg-info/PKG-INFO @@ -1,7 +1,7 @@ Metadata-Version: 2.1 Name: dict2xml -Version: 1.6.1 -Summary: small script to output xml as a string from a python dictionary +Version: 1.7.0 +Summary: Small utility to convert a python dictionary into an XML string Home-page: http://github.com/delfick/python-dict2xml Author: Stephen Moore Author-email: stephen@delfick.com @@ -32,20 +32,21 @@ Description: dict2xml .. code-block:: python - from dict2xml import dict2xml as xmlify + from dict2xml import dict2xml + data = { - 'a' : 1 - , 'b' : [2, 3] - , 'c' : { - 'd' : [ - {'p' : 9} - , {'o' : 10} - ] - , 'e': 7 - } + 'a': 1, + 'b': [2, 3], + 'c': { + 'd': [ + {'p': 9}, + {'o': 10} + ], + 'e': 7 } + } - print xmlify(data, wrap="all", indent=" ") + print dict2xml(data, wrap="all", indent=" ") Output ------ @@ -97,10 +98,16 @@ Description: dict2xml Changelog --------- - 1.6.1 + 1.7.0 - 16 April, 2020 + * Use collections.abc to avoid deprecation warning. Thanks @mangin. + * This library no longer supports Python2 and is only supported for + Python3.6+. Note that the library should still work in Python3.5 as I + have not used f-strings, but the framework I use for the tests is only 3.6+. + + 1.6.1 - August 27, 2019 * Include readme and LICENSE in the package - 1.6 + 1.6 - April 27, 2018 * No code changes * changed the licence to MIT * Added more metadata to pypi diff --git a/dict2xml.egg-info/SOURCES.txt b/dict2xml.egg-info/SOURCES.txt index 2b7eec80841baf2d5876b16cbdb82751773314fc..14af3ec2ccbce65f443f0bac7cb642152aeceba6 100644 --- a/dict2xml.egg-info/SOURCES.txt +++ b/dict2xml.egg-info/SOURCES.txt @@ -1,6 +1,7 @@ LICENSE MANIFEST.in README.rst +pyproject.toml readme.rst setup.py dict2xml/__init__.py diff --git a/dict2xml.egg-info/requires.txt b/dict2xml.egg-info/requires.txt index c97deede2c1cdb0f32a0220ec8bb283a515297a8..515c52d3520a43e9d3c4113300a81a56493c2f6f 100644 --- a/dict2xml.egg-info/requires.txt +++ b/dict2xml.egg-info/requires.txt @@ -1,6 +1,4 @@ -six [tests] -fudge -noseOfYeti>=1.7.0 -nose +noseOfYeti==2.0.1 +pytest==5.3.1 diff --git a/dict2xml/__init__.py b/dict2xml/__init__.py index ad8f7708224c5a27eafe163b1f87955b09b5de2e..7a4654d888f6e850845dc6c87e55c30c8f3d1b0a 100644 --- a/dict2xml/__init__.py +++ b/dict2xml/__init__.py @@ -1,6 +1,9 @@ from dict2xml.logic import Converter, Node + def dict2xml(data, *args, **kwargs): """Return an XML string of a Python dict object.""" return Converter(*args, **kwargs).build(data) + +__all__ = ["dict2xml", "Converter", "Node"] diff --git a/dict2xml/logic.py b/dict2xml/logic.py index 1364168c6dc34cfc194b3e735ff033d2252e4254..45fb5583a0e8c7479defb169356a5dea79ff0ecf 100644 --- a/dict2xml/logic.py +++ b/dict2xml/logic.py @@ -1,19 +1,32 @@ +import collections.abc import collections import re -import six - -NameStartChar = re.compile( - u"(:|[A-Z]|_|[a-z]|[\xC0-\xD6]|[\xD8-\xF6]|[\xF8-\u02FF]|[\u0370-\u037D]|[\u037F-\u1FFF]|[\u200C-\u200D]|[\u2070-\u218F]|[\u2C00-\u2FEF]|[\u3001-\uD7FF]|[\uF900-\uFDCF]|[\uFDF0-\uFFFD])", - re.UNICODE) -NameChar = re.compile( - u"(\-|\.|[0-9]|\xB7|[\u0300-\u036F]|[\u203F-\u2040])", - re.UNICODE) +start_ranges = "|".join( + "[{0}]".format(r) + for r in [ + "\xC0-\xD6", + "\xD8-\xF6", + "\xF8-\u02FF", + "\u0370-\u037D", + "\u037F-\u1FFF", + "\u200C-\u200D", + "\u2070-\u218F", + "\u2C00-\u2FEF", + "\u3001-\uD7FF", + "\uF900-\uFDCF", + "\uFDF0-\uFFFD", + ] +) + +NameStartChar = re.compile(r"(:|[A-Z]|_|[a-z]|{0})".format(start_ranges)) +NameChar = re.compile(r"(\-|\.|[0-9]|\xB7|[\u0300-\u036F]|[\u203F-\u2040])") ######################## ### NODE ######################## + class Node(object): """ Represents each tag in the tree @@ -32,7 +45,7 @@ class Node(object): """ # A mapping of characters to treat as escapable entities and their replacements - entities = [('&', '&'), ('<', '<'), ('>', '>')] + entities = [("&", "&"), ("<", "<"), (">", ">")] def __init__(self, wrap="", tag="", data=None, iterables_repeat_wrap=True): self.tag = self.sanitize_element(tag) @@ -41,7 +54,7 @@ class Node(object): self.type = self.determine_type() self.iterables_repeat_wrap = iterables_repeat_wrap - if self.type == 'flat' and isinstance(self.data, six.string_types): + if self.type == "flat" and isinstance(self.data, str): # Make sure we deal with entities for entity, replacement in self.entities: self.data = self.data.replace(entity, replacement) @@ -52,8 +65,8 @@ class Node(object): wrap = self.wrap end, start = "", "" if wrap: - end = "</%s>" % wrap - start = "<%s>" % wrap + end = "</{0}>".format(wrap) + start = "<{0}>".format(wrap) # Convert the data attached in this node into a value and children value, children = self.convert() @@ -70,13 +83,13 @@ class Node(object): result = [] for c in children: content = c.serialize(indenter) - if c.type == 'flat': + if c.type == "flat": # Child with value, it already is surrounded by the tag result.append(content) else: # Child with children of it's own, they need to be wrapped by start and end content = indenter([content], True) - result.append(''.join((start, content, end))) + result.append("".join((start, content, end))) # We already have what we want, return the indented result return indenter(result, False) @@ -84,12 +97,12 @@ class Node(object): result = [] for c in children: result.append(c.serialize(indenter)) - return ''.join([start, indenter(result, True), end]) + return "".join([start, indenter(result, True), end]) # If here, either: # * Have a value # * Or this node is not an iterable - return ''.join((start, value, content, end)) + return "".join((start, value, content, end)) def determine_type(self): """ @@ -100,14 +113,14 @@ class Node(object): * flat : A string or something that isn't iterable or a mapping """ data = self.data - if isinstance(data, six.string_types) or isinstance(data, six.text_type): - return 'flat' - elif isinstance(data, collections.Mapping): - return 'mapping' - elif isinstance(data, collections.Iterable): - return 'iterable' + if isinstance(data, str): + return "flat" + elif isinstance(data, collections.abc.Mapping): + return "mapping" + elif isinstance(data, collections.abc.Iterable): + return "iterable" else: - return 'flat' + return "flat" def convert(self): """ @@ -122,23 +135,27 @@ class Node(object): data = self.data children = [] - if typ == 'mapping': + if typ == "mapping": sorted_data = data if not isinstance(data, collections.OrderedDict): sorted_data = sorted(data) for key in sorted_data: item = data[key] - children.append(Node(key, "", item, iterables_repeat_wrap=self.iterables_repeat_wrap)) + children.append( + Node(key, "", item, iterables_repeat_wrap=self.iterables_repeat_wrap) + ) - elif typ == 'iterable': + elif typ == "iterable": for item in data: - children.append(Node("", self.wrap, item, iterables_repeat_wrap=self.iterables_repeat_wrap)) + children.append( + Node("", self.wrap, item, iterables_repeat_wrap=self.iterables_repeat_wrap,) + ) else: - val = six.text_type(data) + val = str(data) if self.tag: - val = "<%s>%s</%s>" % (self.tag, val, self.tag) + val = "<{0}>{1}</{2}>".format(self.tag, val, self.tag) return val, children @@ -155,23 +172,26 @@ class Node(object): :ref: http://www.w3.org/TR/REC-xml/#NT-NameChar """ - if wrap and isinstance(wrap, six.string_types): - if wrap.lower().startswith('xml'): - wrap = '_' + wrap - return ''.join( - ['_' if not NameStartChar.match(wrap) else ''] + \ - ['_' if not (NameStartChar.match(c) or NameChar.match(c)) else c - for c in wrap]) + if wrap and isinstance(wrap, str): + if wrap.lower().startswith("xml"): + wrap = "_" + wrap + return "".join( + ["_" if not NameStartChar.match(wrap) else ""] + + ["_" if not (NameStartChar.match(c) or NameChar.match(c)) else c for c in wrap] + ) else: return wrap + ######################## ### CONVERTER ######################## + class Converter(object): """Logic for creating a Node tree and serialising that tree into a string""" - def __init__(self, wrap=None, indent=' ', newlines=True): + + def __init__(self, wrap=None, indent=" ", newlines=True): """ wrap: The tag that the everything else will be contained within indent: The string that is multiplied at the start of each new line, to represent each level of nesting @@ -198,7 +218,7 @@ class Converter(object): def eachline(nodes): """Yield each line in each node""" for node in nodes: - for line in node.split('\n'): + for line in node.split("\n"): yield line def ret(nodes, wrapped): @@ -210,17 +230,18 @@ class Converter(object): and indent each line in the child by one indent unit """ if wrapped: - seperator = "\n%s" % indent - surrounding = "\n%s%%s\n" % indent + seperator = "\n{0}".format(indent) + surrounding = "\n{0}{{0}}\n".format(indent) else: seperator = "\n" - surrounding = "%s" - return surrounding % seperator.join(eachline(nodes)) + surrounding = "{0}" + return surrounding.format(seperator.join(eachline(nodes))) return ret def build(self, data, iterables_repeat_wrap=True): """Create a Node tree from the data and return it as a serialized xml string""" indenter = self._make_indenter() - return Node(wrap=self.wrap, data=data, iterables_repeat_wrap=iterables_repeat_wrap).serialize(indenter) - + return Node( + wrap=self.wrap, data=data, iterables_repeat_wrap=iterables_repeat_wrap + ).serialize(indenter) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000000000000000000000000000000000000..4b418113f17512a23d02dead542521657fdb1cf2 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,11 @@ +[tool.black] +line-length = 100 +include = '\.py$' +exclude = ''' +/( + \.git + | \.tox + | dist + | tools +)/ +''' diff --git a/setup.py b/setup.py index 17a4afca4aa6fba9156dbd8186d9f0bed9262ab6..ce78a53cdbaa1aee86d3ef23c73f4cf84af26acb 100644 --- a/setup.py +++ b/setup.py @@ -1,23 +1,20 @@ from setuptools import setup +# fmt: off + # Setup the project setup( name = "dict2xml" - , version = '1.6.1' + , version = '1.7.0' , packages = ['dict2xml'] , extras_require = { 'tests' : - [ 'fudge' - , 'noseOfYeti>=1.7.0' - , 'nose' + [ "noseOfYeti==2.0.1" + , "pytest==5.3.1" ] } - , install_requires = - [ "six" - ] - , classifiers = [ "Development Status :: 5 - Production/Stable" , "License :: OSI Approved :: MIT License" @@ -33,8 +30,9 @@ setup( , url = "http://github.com/delfick/python-dict2xml" , author = "Stephen Moore" , author_email = "stephen@delfick.com" - , description = "small script to output xml as a string from a python dictionary" + , description = "Small utility to convert a python dictionary into an XML string" , long_description = open("README.rst").read() , license = "MIT" ) +# fmt: on