From 2352b970e7a4086e4d775addcf89ec33206cb6a2 Mon Sep 17 00:00:00 2001 From: Michele Lacchia Date: Mon, 21 Oct 2024 12:16:20 +0200 Subject: [PATCH] Revert "feat: add support for pyproject.toml configuration (#41)" This reverts commit aff681b0c479e09ae1da18f17144cafe9f079337. --- test_xenon.py | 305 ---------------------------------------------- xenon/__init__.py | 186 +++------------------------- 2 files changed, 15 insertions(+), 476 deletions(-) diff --git a/test_xenon.py b/test_xenon.py index 5c2ad69..8f1c72c 100644 --- a/test_xenon.py +++ b/test_xenon.py @@ -3,11 +3,7 @@ import os import sys import unittest -import unittest.mock import collections -import tempfile -import sys -import argparse # Monkey-patch paramunittest for Python 3.10+ if sys.version_info[:2] >= (3, 10): @@ -17,7 +13,6 @@ import httpretty from paramunittest import parametrized -import xenon from xenon import core, api, main @@ -193,305 +188,5 @@ def test_api(self): 'message': 'Resource creation started'}) -class TestCustomConfigParserGetStr(unittest.TestCase): - '''Test class for class CustomConfigParser - getstr method.''' - - @staticmethod - def get_configuration(text): - '''Get CustomConfigParser object with loaded text.''' - with tempfile.TemporaryDirectory() as tmp_dir: - pyproject_path = tmp_dir + '/pyproject.toml' - with open(pyproject_path, "w") as toml_file: - toml_file.write(text) - - configuration = xenon.CustomConfigParser() - configuration.read(pyproject_path) - - return configuration - - def test_missing_section_case(self): - '''Test missing section case.''' - configuration = self.get_configuration( - '[tool.xenon]\n' - 'path = ["path_1", "path_2", "path_3"]\n' - 'max_average = "A"\n' - 'max_average_num = 1.2\n') - - self.assertEqual( - configuration.getstr(xenon.PYPROJECT_SECTION, "maxaverage", "None"), "None") - - def test_with_trim_value_case(self): - '''Test with trim value case.''' - configuration = self.get_configuration( - '[tool.xenon]\n' - 'path = ["path_1", "path_2", "path_3"]\n' - 'max_average = "A"\n' - 'max_modules = \'B\'\n' - 'max_average_num = 1.2\n') - - self.assertEqual( - configuration.getstr(xenon.PYPROJECT_SECTION, "max_average", "None"), "A") - - self.assertEqual( - configuration.getstr(xenon.PYPROJECT_SECTION, "max_modules", "None"), "B") - - def test_without_trim_value_case(self): - '''Test without trim value case.''' - configuration = self.get_configuration( - '[tool.xenon]\n' - 'path = ["path_1", "path_2", "path_3"]\n' - 'max_average = A\n' - 'max_average-num = 1.2\n') - - self.assertEqual( - configuration.getstr(xenon.PYPROJECT_SECTION, "max_average", "None"), "A") - - -class TestCustomConfigParserGetListStr(unittest.TestCase): - '''Test class for class CustomConfigParser - getliststr method.''' - - @staticmethod - def get_configuration(text): - '''Get CustomConfigParser object with loaded text.''' - with tempfile.TemporaryDirectory() as tmp_dir: - pyproject_path = tmp_dir + '/pyproject.toml' - with open(pyproject_path, "w") as toml_file: - toml_file.write(text) - - configuration = xenon.CustomConfigParser() - configuration.read(pyproject_path) - - return configuration - - def test_missing_section_case(self): - '''Test missing section case.''' - configuration = self.get_configuration( - '[tool.xenon]\n' - 'path = ["path_1", "path_2", "path_3"]\n' - 'max_average = "A"\n' - 'max_average-num = 1.2\n') - - self.assertEqual( - configuration.getliststr(xenon.PYPROJECT_SECTION, "maxaverage", "None"), "None") - - def test_parse_error_case(self): - '''Test parse error case.''' - configuration = self.get_configuration( - '[tool.xenon]\n' - 'path = ["path_1", "path_2", "path_3"\n' - 'max_average = "A"\n' - 'max_average-num = 1.2\n') - - self.assertRaisesRegex( - xenon.PyProjectParseError, "path", configuration.getliststr, - xenon.PYPROJECT_SECTION, "path") - - def test_single_value_case(self): - '''Test single value case.''' - configuration = self.get_configuration( - '[tool.xenon]\n' - 'path = "path_1"\n' - 'max_average = "A"\n' - 'max_average-num = 1.2\n') - - self.assertListEqual( - configuration.getliststr(xenon.PYPROJECT_SECTION, "path", None), ["path_1"]) - - def test_invalid_format_case(self): - '''Test invalid format case''' - # Not a list case - configuration = self.get_configuration( - '[tool.xenon]\n' - 'path = {"path_1": "path_2"}\n' - 'max_average = "A"\n' - 'max_average-num = 1.2\n') - - self.assertRaisesRegex( - xenon.PyProjectParseError, "path", configuration.getliststr, - xenon.PYPROJECT_SECTION, "path", None) - - # Not a list of str case - configuration = self.get_configuration( - '[tool.xenon]\n' - 'path = ["path_1", "path_2", true]\n' - 'max_average = "A"\n' - 'max_average-num = 1.2\n') - - self.assertRaisesRegex( - xenon.PyProjectParseError, "path", configuration.getliststr, - xenon.PYPROJECT_SECTION, "path", None) - - def test_multiple_values_case(self): - '''Test multiple values case.''' - configuration = self.get_configuration( - '[tool.xenon]\n' - 'path = ["path_1", "path_2", "path_3"]\n' - 'max_average = "A"\n' - 'max_average-num = 1.2\n') - - self.assertListEqual( - configuration.getliststr(xenon.PYPROJECT_SECTION, "path", None), - ["path_1", "path_2", "path_3"]) - - -class TestParsePyproject(unittest.TestCase): - '''Test class for function parse_pyproject.''' - - def test_parse_error_case(self): - '''Parse error case.''' - with tempfile.TemporaryDirectory() as tmp_dir: - pyproject_path = tmp_dir + '/pyproject.toml' - with open(pyproject_path, "w") as toml_file: - toml_file.write('parameter = value') - - self.assertRaisesRegex( - xenon.PyProjectParseError, "Unable", xenon.parse_pyproject, pyproject_path) - - def test_duplicate_parameteres_case(self): - '''Test duplicate parameters case''' - with tempfile.TemporaryDirectory() as tmp_dir: - pyproject_path = tmp_dir + '/pyproject.toml' - with open(pyproject_path, "w") as toml_file: - toml_file.write( - '[tool.xenon]\n' - 'max_average_num = value_1\n' - 'max_average_num = value_2') - - self.assertRaisesRegex( - xenon.PyProjectParseError, "duplicate parameters", xenon.parse_pyproject, pyproject_path) - - def test_missing_file_case(self): - '''Test missing file path case.''' - with tempfile.TemporaryDirectory() as tmp_dir: - self.assertDictEqual(xenon.parse_pyproject(tmp_dir), {}) - - def test_invalid_max_average_num_type_case(self): - '''Test invalid max-average-num parameter type case''' - with tempfile.TemporaryDirectory() as tmp_dir: - pyproject_path = tmp_dir + '/pyproject.toml' - with open(pyproject_path, "w") as toml_file: - toml_file.write('[tool.xenon]\nmax_average_num = value') - - self.assertRaisesRegex( - xenon.PyProjectParseError, "max_average_num", xenon.parse_pyproject, pyproject_path) - - def test_invalid_no_assert_type_case(self): - '''Test invalid no_assert parameter type case''' - with tempfile.TemporaryDirectory() as tmp_dir: - pyproject_path = tmp_dir + '/pyproject.toml' - with open(pyproject_path, "w") as toml_file: - toml_file.write('[tool.xenon]\nno_assert = next') - - self.assertRaisesRegex( - xenon.PyProjectParseError, "no_assert", xenon.parse_pyproject, pyproject_path) - - def test_all_parameters_case(self): - '''Test all parameters case.''' - with tempfile.TemporaryDirectory() as tmp_dir: - pyproject_path = tmp_dir + '/pyproject.toml' - with open(pyproject_path, "w") as toml_file: - toml_file.write( - '[tool.xenon]\n' - 'path = ["path_1", "path_2", "path_3"]\n' - 'max_average = "A"\n' - 'max_average_num = 1.2\n' - 'max_modules = "B"\n' - 'max_absolute = "C"\n' - 'exclude = ["path_4", "path_5"]\n' - 'ignore = ["path_6", "path_7"]\n' - 'no_assert = true') - - result = xenon.parse_pyproject(pyproject_path) - - self.assertDictEqual( - xenon.parse_pyproject(pyproject_path), { - "path": ["path_1", "path_2", "path_3"], - "average": 'A', - "averagenum": 1.2, - "modules": 'B', - "absolute": 'C', - "url": None, - "config": None, - "exclude": 'path_4,path_5', - "ignore": 'path_6,path_7', - "no_assert": True}) - - -class TestGetParserDefaultsAndDeleteDefaults(unittest.TestCase): - '''Test class for function get_parser_defaults_and_delete_defaults.''' - - def test_valid_case(self): - '''Test valid case''' - parser = argparse.ArgumentParser(add_help=False) - - parser.add_argument("-t", "--test", default="test") - parser.add_argument("-v", "--values", default="values", dest="val") - parser.add_argument("-w", "--without") - - args_defualt = xenon.get_parser_defaults_and_delete_defaults(parser) - - self.assertEqual(args_defualt, {"test": "test", "val": "values"}) - - sys.argv = ["file.py"] - - parser.parse_args() - - self.assertEqual( - vars(parser.parse_args()), {"test": None, "val": None, "without": None}) - - -class TestParseArgs(unittest.TestCase): - '''Test class for function parse_args.''' - - def test_cmd_pyproject_case(self): - '''Test parameter set from cmd and pyproject case.''' - with tempfile.TemporaryDirectory() as tmp_dir: - pyproject_path = tmp_dir + '/pyproject.toml' - with open(pyproject_path, "w") as toml_file: - toml_file.write( - '[tool.xenon]\n' - 'max_average = "A"\n') - - sys.argv = ["file", "-a", "B"] - - args = xenon.parse_args(pyproject_path) - self.assertEqual(args.average, "B") - - def test_cmd_default_value_case(self): - '''Test parameter not set from cmd (with default value) and pyproject case.''' - with tempfile.TemporaryDirectory() as tmp_dir: - pyproject_path = tmp_dir + '/pyproject.toml' - with open(pyproject_path, "w") as toml_file: - toml_file.write( - '[tool.xenon]\n' - 'config_file = config_file_path\n') - - sys.argv = ["file"] - - args = xenon.parse_args(pyproject_path) - self.assertEqual(args.config, "config_file_path") - - def test_cmd_non_default_value_case(self): - '''Test parameter set from cmd (with default value) and pyproject case.''' - with tempfile.TemporaryDirectory() as tmp_dir: - pyproject_path = tmp_dir + '/pyproject.toml' - with open(pyproject_path, "w") as toml_file: - toml_file.write( - '[tool.xenon]\n' - 'config_file = config_file_path\n') - - # Set different value in cmd then default - sys.argv = ["file", "--config-file", "cfg_file_path"] - - args = xenon.parse_args(pyproject_path) - self.assertEqual(args.config, "cfg_file_path") - - # Set same value in cmd as default - sys.argv = ["file", "--config-file", ".xenon.yml"] - - args = xenon.parse_args(pyproject_path) - self.assertEqual(args.config, ".xenon.yml") - - if __name__ == '__main__': unittest.main() diff --git a/xenon/__init__.py b/xenon/__init__.py index 5d12c54..85d748f 100644 --- a/xenon/__init__.py +++ b/xenon/__init__.py @@ -7,129 +7,20 @@ import os import sys import logging -import json -import argparse -# Import ConfigParser based on python version -if sys.version_info[0] == 2: - import ConfigParser as configparser -else: - import configparser - -PYPROJECT_SECTION = "tool.xenon" - - -class PyProjectParseError(Exception): - '''Exception for pyproject.toml parser.''' - def __init__(self, msg): - super(PyProjectParseError, self).__init__(msg) - - -class CustomConfigParser(configparser.ConfigParser): - '''Custom ConfigParser.''' - - def getstr(self, section, option, fallback=object()): - '''Get required option which is strings''' - values = self.get(section, option, fallback=fallback) - if values == fallback: - return fallback - - # Trim " or ' - if (values[0] == '"' and values[-1] == '"') or (values[0] == '\'' and values[-1] == '\''): - return values[1:-1] - - return values - - def getliststr(self, section, option, fallback=object()): - '''Get required option which is list of strings.''' - values = self.get(section, option, fallback=fallback) - if values == fallback: - return fallback - - # Conver string to python object - try: - values = json.loads(values) - except json.decoder.JSONDecodeError: - raise PyProjectParseError("Invalid format of parameter %s" % option) - - # Single parameter - if isinstance(values, str): - return [values] - - # Check format - list - if not isinstance(values, list): - raise PyProjectParseError("Invalid format of parameter %s" % option) - - # Check items - for value in values: - if not isinstance(value, str): - raise PyProjectParseError("Invalid format of parameter %s" % option) - - return values - - -def parse_pyproject(file_path): - '''Parse pyproject.toml file.''' - pyproject_parameters = {} - - configuration = CustomConfigParser() - - # Invalid format - missing any section [] - try: - loaded_files = configuration.read(file_path) - except configparser.MissingSectionHeaderError: - raise PyProjectParseError("Unable to parse %s" %file_path) - except configparser.DuplicateOptionError: - raise PyProjectParseError("%s contain duplicate parameters" %file_path) - - # Pyproject.toml does not exists - if not loaded_files: - return pyproject_parameters - - # Parse single string values - for parameter in (("max_average", "average"), - ("max_modules", "modules"), - ("max_absolute", "absolute"), - ("url", "url"), - ("config_file", "config")): - pyproject_parameters[parameter[1]] = configuration.getstr( - PYPROJECT_SECTION, parameter[0], fallback=None) - - # Parse list of string to str - for parameter in (("exclude", "exclude"), ("ignore", "ignore")): - values = configuration.getliststr(PYPROJECT_SECTION, parameter[0], None) - if values: - pyproject_parameters[parameter[1]] = ",".join(values) - else: - pyproject_parameters[parameter[1]] = None - - # Parse list of string as list - pyproject_parameters["path"] = configuration.getliststr( - PYPROJECT_SECTION, "path", fallback=None) - - try: - pyproject_parameters["averagenum"] = configuration.getfloat( - PYPROJECT_SECTION, "max_average_num", fallback=None) - except ValueError: - raise PyProjectParseError("Invalid format of parameter max_average_num") - - try: - pyproject_parameters["no_assert"] = configuration.getboolean( - PYPROJECT_SECTION, "no_assert", fallback=None) - except ValueError: - raise PyProjectParseError("Invalid format of parameter no_assert") - - return pyproject_parameters - +def parse_args(): + '''Parse arguments from the command line and read the config file (for the + REPO_TOKEN value). + ''' + import yaml + import argparse -def get_parser(): - '''Get parser.''' parser = argparse.ArgumentParser() parser.add_argument('-v', '--version', action='version', version=__version__) - parser.add_argument('-p', '--path', help='Directory containing source files to ' - 'analyze, or multiple file paths', nargs='+', default='*.py') + parser.add_argument('path', help='Directory containing source files to ' + 'analyze, or multiple file paths', nargs='+') parser.add_argument('-a', '--max-average', dest='average', metavar='', help='Letter grade threshold for the average complexity') parser.add_argument('--max-average-num', dest='averagenum', type=float, @@ -157,52 +48,13 @@ def get_parser(): parser.add_argument('--paths-in-front', dest='paths_in_front', action='store_true', help='Print block and module complexity with log line starting with their path') - return parser - - -def get_parser_defaults_and_delete_defaults(parser): - '''Get defaults of parser and delete them in parser.''' - default_values = {} - - for argument in parser._actions: - if argument.default is not None: - default_values[argument.dest] = argument.default - argument.default = None - - return default_values - - -def parse_args(pyproject_file): - '''Parse arguments from the command line and read the config file (for the - REPO_TOKEN value). - ''' - import yaml - - args = argparse.Namespace() - - parser = get_parser() - args_default = get_parser_defaults_and_delete_defaults(parser) - args_cmd = parser.parse_args() - - # Include default values - for arg_name, arg_value in args_default.items(): - setattr(args, arg_name, arg_value) - - # Include args from pyproject.toml file - pyproject_args = parse_pyproject(pyproject_file) - for arg_name, arg_value in pyproject_args.items(): - # Argument not set in pyproject.toml - if not arg_value: + args = parser.parse_args() + # normalize the rank + for attr in ('absolute', 'modules', 'average'): + val = getattr(args, attr, None) + if val is None: continue - - # Set only in pyproject.toml - setattr(args, arg_name, arg_value) - - # Include args from cmd - for arg_name, arg_value in vars(args_cmd).items(): - if arg_value is not None: - setattr(args, arg_name, arg_value) - + setattr(args, attr, val.upper()) try: with open(args.config, 'r') as f: yml = yaml.safe_load(f) @@ -213,14 +65,6 @@ def parse_args(pyproject_file): os.environ.get('BARIUM_REPO_TOKEN', '')) args.service_name = yml.get('service_name', 'travis-ci') args.service_job_id = os.environ.get('TRAVIS_JOB_ID', '') - - # normalize the rank - for attr in ('absolute', 'modules', 'average'): - val = getattr(args, attr, None) - if val is None: - continue - setattr(args, attr, val.upper()) - return args @@ -232,7 +76,7 @@ def main(args=None): from xenon.core import analyze from xenon.repository import gitrepo - args = args or parse_args("pyproject.toml") + args = args or parse_args() if args.paths_in_front: # Skip the level and module name to have log line starting with message # When using xenon in PyCharm terminal, one can benefit from