diff --git a/pyhocon/config_parser.py b/pyhocon/config_parser.py index 79b5ee7f..904a5ed0 100644 --- a/pyhocon/config_parser.py +++ b/pyhocon/config_parser.py @@ -8,7 +8,8 @@ import socket import sys from datetime import timedelta -from glob import glob + +import pyparsing from pyparsing import (Forward, Group, Keyword, Literal, Optional, ParserElement, ParseSyntaxException, QuotedString, @@ -16,6 +17,18 @@ Word, ZeroOrMore, alphanums, alphas8bit, col, lineno, replaceWith) +# Fix deepcopy issue with pyparsing +if sys.version_info >= (3, 8): + def fixed_get_attr(self, item): + if item == '__deepcopy__': + raise AttributeError(item) + try: + return self[item] + except KeyError: + return "" + + pyparsing.ParseResults.__getattr__ = fixed_get_attr + import asset from pyhocon.config_tree import (ConfigInclude, ConfigList, ConfigQuotedString, ConfigSubstitution, ConfigTree, @@ -23,6 +36,7 @@ from pyhocon.exceptions import (ConfigException, ConfigMissingException, ConfigSubstitutionException) + use_urllib2 = False try: # For Python 3.0 and later @@ -46,6 +60,8 @@ def glob(pathname, recursive=False): warnings.warn('This version of python (%s) does not support recursive import' % sys.version) from glob import glob as _glob return _glob(pathname) +else: + from glob import glob logger = logging.getLogger(__name__) @@ -350,6 +366,7 @@ def include_config(instring, loc, token): def _make_prefix(path): return ('' if path is None else '[%s]' % path).ljust(55).replace('\\', '/') + _prefix = _make_prefix(path) def _load(path): @@ -376,7 +393,9 @@ def _merge(a, b): elif isinstance(a, list) and isinstance(b, list): return a + b else: - raise ConfigException('Unable to make such include (merging unexpected types: {a} and {b}', a=type(a), b=type(b)) + raise ConfigException('Unable to make such include (merging unexpected types: {a} and {b}', + a=type(a), b=type(b)) + logger.debug('%s Loading following configs: %s', _prefix, paths) for p in paths: obj = _merge(obj, _load(p)) diff --git a/pyhocon/config_tree.py b/pyhocon/config_tree.py index 48c38847..511fc1ce 100644 --- a/pyhocon/config_tree.py +++ b/pyhocon/config_tree.py @@ -1,16 +1,15 @@ from collections import OrderedDict -from pyparsing import lineno -from pyparsing import col +from pyparsing import col, lineno +import re +import copy +from pyhocon.exceptions import ConfigException, ConfigWrongTypeException, ConfigMissingException + try: basestring except NameError: # pragma: no cover basestring = str unicode = str -import re -import copy -from pyhocon.exceptions import ConfigException, ConfigWrongTypeException, ConfigMissingException - class UndefinedKey(object): pass @@ -148,7 +147,8 @@ def _get(self, key_path, key_index=0, default=UndefinedKey): if elt is UndefinedKey: if default is UndefinedKey: - raise ConfigMissingException(u"No configuration setting found for key {key}".format(key='.'.join(key_path[:key_index + 1]))) + raise ConfigMissingException( + u"No configuration setting found for key {key}".format(key='.'.join(key_path[:key_index + 1]))) else: return default @@ -181,7 +181,8 @@ def parse_key(string): :return: """ special_characters = '$}[]:=+#`^?!@*&.' - tokens = re.findall(r'"[^"]+"|[^{special_characters}]+'.format(special_characters=re.escape(special_characters)), string) + tokens = re.findall( + r'"[^"]+"|[^{special_characters}]+'.format(special_characters=re.escape(special_characters)), string) def contains_special_character(token): return any((c in special_characters) for c in token) @@ -414,6 +415,7 @@ def as_plain_ordered_dict(self): :return: this config as an OrderedDict :type return: OrderedDict """ + def plain_value(v): if isinstance(v, list): return [plain_value(e) for e in v] diff --git a/pyhocon/converter.py b/pyhocon/converter.py index 404c7571..cf9d5c30 100644 --- a/pyhocon/converter.py +++ b/pyhocon/converter.py @@ -14,6 +14,7 @@ basestring except NameError: basestring = str + unicode = str class HOCONConverter(object): @@ -33,13 +34,7 @@ def to_json(cls, config, compact=False, indent=2, level=0): bet_lines = [] for key, item in config.items(): new_key = key.strip('"') # for dotted keys enclosed with "" to not be interpreted as nested key - if isinstance(new_key, unicode): - new_key = new_key.encode('utf-8') - new_value = cls.to_json(item, compact, indent, level + 1) - if isinstance(new_value, unicode): - new_value = new_value.encode('utf-8') - bet_lines.append('{indent}"{key}": {value}'.format( indent=''.rjust((level + 1) * indent, ' '), key=new_key, diff --git a/setup.py b/setup.py index 46ce7f8f..6f6c2a38 100755 --- a/setup.py +++ b/setup.py @@ -46,6 +46,7 @@ def run_tests(self): 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8' ], packages=[ 'pyhocon', diff --git a/tests/test_config_parser.py b/tests/test_config_parser.py index e3d3caa4..af833c33 100644 --- a/tests/test_config_parser.py +++ b/tests/test_config_parser.py @@ -5,6 +5,7 @@ import tempfile from collections import OrderedDict from datetime import timedelta + from pyparsing import ParseBaseException, ParseException, ParseSyntaxException import asset import mock @@ -13,7 +14,6 @@ from pyhocon.exceptions import (ConfigException, ConfigMissingException, ConfigWrongTypeException) - try: from dateutil.relativedelta import relativedelta as period except Exception: @@ -1085,9 +1085,13 @@ def test_include_glob_list_from_samples(self): config = ConfigFactory.parse_file("samples/all_bars.conf") bars = config.get_list('bars') assert len(bars) == 10 - assert bars[0].get('name') == 'Bloody Mary' - assert bars[5].get('name') == 'Homer\'s favorite coffee' - assert bars[9].get('type') == 'milk' + + names = {bar['name'] for bar in bars} + types = {bar['type'] for bar in bars if 'type' in bar} + print(types, '(((((') + assert 'Bloody Mary' in names + assert 'Homer\'s favorite coffee' in names + assert 'milk' in types def test_list_of_dicts(self): config = ConfigFactory.parse_string( @@ -1269,10 +1273,10 @@ def test_include_asset_file(self, monkeypatch): fdin.flush() def load(*args, **kwargs): - class File(object): def __init__(self, filename): self.filename = filename + return File(fdin.name) monkeypatch.setattr(asset, "load", load)