From a06cce38267f41dbabdb40f1756910a7315acd6a Mon Sep 17 00:00:00 2001 From: Francois Dang Ngoc Date: Wed, 18 Mar 2026 21:01:18 -0500 Subject: [PATCH] release 0.3.62 --- CHANGELOG.md | 4 ++++ README.md | 3 ++- pyhocon/config_parser.py | 36 +++++++++++++++++++----------------- pyhocon/converter.py | 1 - pyhocon/period_parser.py | 4 ++-- setup.cfg | 2 +- setup.py | 29 +---------------------------- tests/test_config_parser.py | 15 ++++++--------- tests/test_periods.py | 2 -- tox.ini | 8 +++++--- 10 files changed, 40 insertions(+), 64 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bf873af..ed7866d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +# Version 0.3.62 + +* Migrate to pyparsing 3.x and drop Python 2.7 support (@alejandrorm) [#337] + # Version 0.3.61 * fix(tox): remove old EOLed python 3.x versions, added new python versions (@pierresouchay) [#330] diff --git a/README.md b/README.md index c37bccb5..57c7e49f 100644 --- a/README.md +++ b/README.md @@ -393,6 +393,7 @@ assert config == d - Carol Guo ([@carolguo-dd](https://github.com/carolguo-dd)) - Jakub KubĂ­k ([@M0dEx](https://github.com/M0dEx)) - Jakub Szewczyk ([@jakub-szewczyk-exa](https://github.com/jakub-szewczyk-exa)) + - Alejandro Rodriguez-Morantes ([@alejandrorm](https://github.com/alejandrorm)) ### Thanks @@ -408,4 +409,4 @@ assert config == d - Dominik1123 ([@Dominik1123](https://github.com/Dominik1123)) - Richard Taylor ([@richard534](https://github.com/richard534)) - Sergii Lutsanych ([@sergii1989](https://github.com/sergii1989)) - + - Romain G ([@Romain-DE](https://github.com/Romain-DE)) diff --git a/pyhocon/config_parser.py b/pyhocon/config_parser.py index de0ee993..d41f09ec 100644 --- a/pyhocon/config_parser.py +++ b/pyhocon/config_parser.py @@ -26,7 +26,6 @@ def fixed_get_attr(self, item): except KeyError: return "" - pyparsing.ParseResults.__getattr__ = fixed_get_attr from pyhocon.config_tree import (ConfigInclude, ConfigList, ConfigQuotedString, @@ -50,6 +49,7 @@ def find_package_dirs(name): raise ImportError('No module named {!r}'.format(name)) return spec.submodule_search_locations + logger = logging.getLogger(__name__) @@ -350,7 +350,7 @@ def set_default_white_spaces(): false_expr = Keyword("false", case_insensitive=True).set_parse_action(replace_with(False)) null_expr = Keyword("null", case_insensitive=True).set_parse_action(replace_with(NoneValue())) key = QuotedString('"""', esc_char='\\', unquote_results=False) | \ - QuotedString('"', esc_char='\\', unquote_results=False) | Word(alphanums + alphas8bit + '._- /') + QuotedString('"', esc_char='\\', unquote_results=False) | Word(alphanums + alphas8bit + '._- /') eol = Word('\n\r').suppress() eol_comma = Word('\n\r,').suppress() @@ -377,15 +377,16 @@ def set_default_white_spaces(): value_expr = get_period_expr() | number_expr | true_expr | false_expr | null_expr | string_expr include_content = ( - quoted_string | ((Keyword('url') | Keyword('file') | Keyword('package')) - Literal( - '(').suppress() - quoted_string - Literal(')').suppress()) + quoted_string | ((Keyword('url') | Keyword('file') | Keyword('package')) + - Literal('(').suppress() - quoted_string + - Literal(')').suppress()) ) include_expr = ( - Keyword("include", case_insensitive=True).suppress() + ( + Keyword("include", case_insensitive=True).suppress() + ( include_content | ( - Keyword("required") - Literal('(').suppress() - include_content - Literal(')').suppress() - ) - ) + Keyword("required") - Literal('(').suppress() - include_content - Literal(')').suppress() + ) + ) ).set_parse_action(include_config) root_dict_expr = Forward() @@ -407,19 +408,20 @@ def set_default_white_spaces(): # special case when we have a value assignment where the string can potentially be the remainder of the line assign_expr << Group( key - ZeroOrMore(comment_no_comma_eol) - ( - dict_expr | (Literal('=') | Literal(':') | Literal('+=')) - ZeroOrMore( - comment_no_comma_eol) - ConcatenatedValueParser(multi_value_expr)) + dict_expr | (Literal('=') | Literal(':') | Literal('+=')) - ZeroOrMore( + comment_no_comma_eol) - ConcatenatedValueParser(multi_value_expr)) ) # the file can be { ... } where {} can be omitted or [] config_expr = ZeroOrMore(comment_eol | eol) + ( - list_expr | root_dict_expr | inside_root_dict_expr) + ZeroOrMore( + list_expr | root_dict_expr | inside_root_dict_expr + ) + ZeroOrMore( comment_eol | eol_comma) config = config_expr.parse_string(content, parse_all=True)[0] if resolve: - allow_unresolved = resolve and unresolved_value is not DEFAULT_SUBSTITUTION \ - and unresolved_value is not MANDATORY_SUBSTITUTION + allow_unresolved = resolve and unresolved_value is not DEFAULT_SUBSTITUTION + allow_unresolved = allow_unresolved and unresolved_value is not MANDATORY_SUBSTITUTION has_unresolved = cls.resolve_substitutions(config, allow_unresolved) if has_unresolved and unresolved_value is MANDATORY_SUBSTITUTION: raise ConfigSubstitutionException( @@ -535,8 +537,8 @@ def _do_substitute(cls, substitution, resolved_value, is_optional_resolved=True) # if it is a string, then add the extra ws that was present in the original string after the substitution formatted_resolved_value = resolved_value \ if resolved_value is None \ - or isinstance(resolved_value, (dict, list)) \ - or substitution.index == len(config_values.tokens) - 1 \ + or isinstance(resolved_value, (dict, list)) \ + or substitution.index == len(config_values.tokens) - 1 \ else (str(resolved_value) + substitution.ws) # use a deepcopy of resolved_value to avoid mutation config_values.put(substitution.index, copy.deepcopy(formatted_resolved_value)) @@ -606,11 +608,11 @@ def resolve_substitutions(cls, config, accept_unresolved=False): is_optional_resolved, resolved_value = cls._resolve_variable(config, substitution) - if isinstance(resolved_value, ConfigValues) : + if isinstance(resolved_value, ConfigValues): resolved_value = resolved_value.transform() value_to_be_substitute = resolved_value if overridden_value and not isinstance(overridden_value, ConfigValues): - value_to_be_substitute = overridden_value + value_to_be_substitute = overridden_value unresolved, _, _ = cls._do_substitute(substitution, value_to_be_substitute, is_optional_resolved) any_unresolved = unresolved or any_unresolved diff --git a/pyhocon/converter.py b/pyhocon/converter.py index ee40029e..158437f1 100644 --- a/pyhocon/converter.py +++ b/pyhocon/converter.py @@ -284,4 +284,3 @@ def _escape_match(cls, match): @classmethod def _escape_string(cls, string): return re.sub(r'[\x00-\x1F"\\]', cls._escape_match, string) - diff --git a/pyhocon/period_parser.py b/pyhocon/period_parser.py index fc25f1a2..2ed745ea 100644 --- a/pyhocon/period_parser.py +++ b/pyhocon/period_parser.py @@ -62,8 +62,8 @@ def get_period_expr(): # Allow only spaces as a valid separator between value and unit. # E.g. \t as a separator is invalid: '10weeks'. return Combine( - Word(nums)('value') + ZeroOrMore(Literal(" ")).suppress() + Or(period_types)('unit') + WordEnd( - alphanums).suppress() + Word(nums)('value') + ZeroOrMore(Literal(" ")).suppress() + Or(period_types)('unit') + WordEnd( + alphanums).suppress() ).set_parse_action(convert_period) diff --git a/setup.cfg b/setup.cfg index b0db9692..385fe00e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [flake8] -ignore = +ignore = W503 max-line-length = 160 statistics = True count = True diff --git a/setup.py b/setup.py index 3218eb2b..3f8e94ec 100755 --- a/setup.py +++ b/setup.py @@ -1,32 +1,10 @@ #!/usr/bin/env python -import sys - from setuptools import setup -from setuptools.command.test import test as TestCommand - - -class PyTestCommand(TestCommand): - user_options = [('pytest-args=', 'a', "Arguments to pass to py.test")] - - def initialize_options(self): - TestCommand.initialize_options(self) - self.pytest_args = [] - - def finalize_options(self): - TestCommand.finalize_options(self) - self.test_args = [] - self.test_suite = True - - def run_tests(self): - import pytest - errno = pytest.main(self.pytest_args) - sys.exit(errno) - setup( name='pyhocon', - version='0.3.61', + version='0.3.62', description='HOCON parser for Python', long_description='pyhocon is a HOCON parser for Python. Additionally we provide a tool (pyhocon) to convert any HOCON ' 'content into json, yaml and properties format.', @@ -56,14 +34,9 @@ def run_tests(self): extras_require={ 'Duration': ['python-dateutil>=2.8.0'] }, - tests_require=['pytest', 'mock==3.0.5'], entry_points={ 'console_scripts': [ 'pyhocon=pyhocon.tool:main' ] - }, - test_suite='tests', - cmdclass={ - 'test': PyTestCommand } ) diff --git a/tests/test_config_parser.py b/tests/test_config_parser.py index e81fdcd1..6e0cae22 100644 --- a/tests/test_config_parser.py +++ b/tests/test_config_parser.py @@ -28,6 +28,7 @@ except Exception: from datetime import timedelta as period + class TestConfigParser(object): def test_parse_simple_value(self): config = ConfigFactory.parse_string( @@ -118,7 +119,6 @@ def test_parse_with_enclosing_brace_and_period_like_value(self): assert config.get_string('a.b') == '5' assert config.get_string('a.y_min') == '42' - def test_issue_324(self): config = ConfigFactory.parse_string("a { c = 3\nd = 4 }") assert config["a"]["c"] == 3 @@ -194,11 +194,10 @@ def test_parse_with_list_mixed_types_with_durations_and_trailing_comma(self): # Depending if parsing dates is enabled, might parse date or might not # since this is an optional dependency assert ( - config['b'] == ['a', 1, period(weeks=10), period(minutes=5)] - ) or ( - config['b'] == ['a', 1, '10 weeks', '5 minutes'] - ) - + config['b'] == ['a', 1, period(weeks=10), period(minutes=5)] + ) or ( + config['b'] == ['a', 1, '10 weeks', '5 minutes'] + ) def test_parse_with_enclosing_square_bracket(self): config = ConfigFactory.parse_string("[1, 2, 3]") @@ -1757,12 +1756,11 @@ def test_override_optional_substitution(self): result = ${test} """) assert config == { - 'a' : 3, + 'a': 3, 'test': 3, 'result': 3 } - def test_substitution_cycle(self): with pytest.raises(ConfigSubstitutionException): ConfigFactory.parse_string( @@ -2701,7 +2699,6 @@ def test_triple_quotes_keys_triple_quotes_values_second_separator(self): try: from dateutil.relativedelta import relativedelta - @pytest.mark.parametrize('data_set', [ ('a: 1 months', relativedelta(months=1)), ('a: 1months', relativedelta(months=1)), diff --git a/tests/test_periods.py b/tests/test_periods.py index f5eb8e60..358c130f 100644 --- a/tests/test_periods.py +++ b/tests/test_periods.py @@ -62,7 +62,6 @@ def test_parse_string_with_duration(data_set): try: from dateutil.relativedelta import relativedelta - @pytest.mark.parametrize('data_set', [ ('1 months', relativedelta(months=1)), ('1months', relativedelta(months=1)), @@ -82,7 +81,6 @@ def test_parse_string_with_duration_optional_units(data_set): assert parsed == data_set[1] - def test_format_relativedelta(): for time_delta, expected_result in ((relativedelta(seconds=0), '0 seconds'), diff --git a/tox.ini b/tox.ini index d3cffe15..157e4ccf 100644 --- a/tox.ini +++ b/tox.ini @@ -2,17 +2,19 @@ envlist = flake8, py{37,38,39,310,311,312,313} [testenv] -passenv = TRAVIS TRAVIS_JOB_ID TRAVIS_BRANCH +passenv = TRAVIS,TRAVIS_JOB_ID,TRAVIS_BRANCH deps = pytest coveralls + setuptools + mock python-dateutil>=2.8.0 + PyYAML # for python 3.4 typing commands = - coverage run --source=pyhocon setup.py test + coverage run --source=pyhocon -m pytest tests coverage report -m - coveralls [testenv:flake8] basepython = python