diff --git a/setup.cfg b/setup.cfg index fecec60..6251af0 100644 --- a/setup.cfg +++ b/setup.cfg @@ -13,8 +13,10 @@ ignore_missing_imports = True # -- flake8 library config [flake8] -# - list of ignored issues (E501 - too long line) -#ignore = E501 +# - list of ignored issues: +# E501 - too long line +# E203 - whitespace before ':' - black formatter puts it +ignore = E203 # - max cognitive complexity for statements max-complexity = 10 # - adjustment to align with line length for black (it uses 88 chars by default) diff --git a/src/pyutilities/commands/pygit.py b/src/pyutilities/commands/pygit.py index b79de1a..68a2502 100644 --- a/src/pyutilities/commands/pygit.py +++ b/src/pyutilities/commands/pygit.py @@ -5,13 +5,14 @@ Some useful/convenient functions related to GIT. Created: Dmitrii Gusev, 03.05.2019 - Modified: Dmitrii Gusev, 14.10.2022 + Modified: Dmitrii Gusev, 25.11.2022 """ import logging from subprocess import Popen from pyutilities.utils.common_utils import myself from pyutilities.exception import PyUtilitiesException +from pyutilities.defaults import MSG_MODULE_ISNT_RUNNABLE log = logging.getLogger(__name__) log.addHandler(logging.NullHandler()) @@ -101,4 +102,4 @@ class GitException(Exception): if __name__ == "__main__": - print("pyutilities.pygit: Don't try to execute library as a standalone app!") + print(MSG_MODULE_ISNT_RUNNABLE) diff --git a/src/pyutilities/commands/pymaven.py b/src/pyutilities/commands/pymaven.py index a9795a8..6f45f1e 100644 --- a/src/pyutilities/commands/pymaven.py +++ b/src/pyutilities/commands/pymaven.py @@ -6,7 +6,7 @@ Functions are incapsulated in PyMaven class. Created: Dmitrii Gusev, 02.05.2019 - Modified: Dmitrii Gusev, 24.11.2022 + Modified: Dmitrii Gusev, 25.11.2022 """ import os @@ -16,6 +16,7 @@ from subprocess import Popen from pyutilities.utils.common_utils import myself from pyutilities.exception import PyUtilitiesException +from pyutilities.defaults import MSG_MODULE_ISNT_RUNNABLE log = logging.getLogger(__name__) log.addHandler(logging.NullHandler()) @@ -94,3 +95,7 @@ def sources(self, location): raise PyUtilitiesException(f"Process returned non zero exit code [{process.returncode}]!") except AttributeError as se: log.error(f"Error downloading sources for repo [{location}]! {se}") + + +if __name__ == "__main__": + print(MSG_MODULE_ISNT_RUNNABLE) diff --git a/src/pyutilities/config/configuration.py b/src/pyutilities/config/configuration.py index 4dcfa99..192887c 100755 --- a/src/pyutilities/config/configuration.py +++ b/src/pyutilities/config/configuration.py @@ -12,7 +12,7 @@ 24.11.2022 Various refactorings, added some typing. Created: Gusev Dmitrii, XX.08.2017 - Modified: Gusev Dmitrii, 24.11.2022 + Modified: Gusev Dmitrii, 25.11.2022 """ import os @@ -22,6 +22,7 @@ from string import Template from pyutilities.io.io_utils import read_yaml +from pyutilities.defaults import MSG_MODULE_ISNT_RUNNABLE YAML_EXTENSION_1 = ".yml" YAML_EXTENSION_2 = ".yaml" @@ -103,11 +104,9 @@ def load(self, path: str | None, is_merge_env=True): # todo: extract two methods - load from file/load from dir + refactor unit tests # if provided path to single file - load it, otherwise - load from directory if os.path.isfile(path) and (path.endswith(YAML_EXTENSION_1) or path.endswith(YAML_EXTENSION_2)): - self.log.debug("Provided path [{}] is a YAML file. Loading.".format(path)) - try: - self.merge_dict(read_yaml(path)) - except ConfigError as ex: - raise ConfigError("ERROR while merging file %s to configuration.\n%s" % (path, ex)) + self.log.debug("Provided path [{}] is a YAML file.".format(path)) + self.log.debug("Loading configuration from [{}].".format(path)) + self.merge_dict(read_yaml(path)) # loading from directory (all YAML files) elif os.path.isdir(path): @@ -118,12 +117,7 @@ def load(self, path: str | None, is_merge_env=True): some_file.endswith(YAML_EXTENSION_1) or some_file.endswith(YAML_EXTENSION_2) ): self.log.debug("Loading configuration from [{}].".format(some_file)) - try: - self.merge_dict(read_yaml(file_path)) - except ConfigError as ex: - raise ConfigError( - "ERROR while merging file %s to configuration.\n%s" % (file_path, ex) - ) + self.merge_dict(read_yaml(file_path)) # unknown file/dir type else: @@ -148,7 +142,6 @@ def merge_dict(self, new_dict): self.config_dict = result else: self.config_dict.update(new_dict) - return def __add_entity__(self, dict1, dict2, current_key=""): """Adds second dictionary to the first (processing nested dicts recursively) @@ -350,4 +343,4 @@ def load_dict_from_xls(self, path_to_xls, config_sheet_name): if __name__ == "__main__": - print("pyutilities.config: Don't try to execute library as a standalone app!") + print(MSG_MODULE_ISNT_RUNNABLE) diff --git a/src/pyutilities/utils/string_utils.py b/src/pyutilities/utils/string_utils.py index 0e3aaa8..7cb9e40 100644 --- a/src/pyutilities/utils/string_utils.py +++ b/src/pyutilities/utils/string_utils.py @@ -6,11 +6,11 @@ to module String in java library Apache Commons). Created: Dmitrii Gusev, 15.04.2019 - Modified: Dmitrii Gusev, 24.11.2022 + Modified: Dmitrii Gusev, 25.11.2022 """ import logging -from typing import Tuple, Dict, AnyStr +from typing import Tuple, Dict from pyutilities.exception import PyUtilitiesException from pyutilities.defaults import MSG_MODULE_ISNT_RUNNABLE @@ -21,37 +21,20 @@ log.addHandler(logging.NullHandler()) -def is_str_empty(string: AnyStr | None) -> bool: - """Check is string empty/NoNe or not. - :param string: - :return: - """ - if string is None or not string or not string.strip(): # check for whitespaces string - return True - - return False # all checks passed - - -def trim_to_none(string: str): - """Trim the provided string to None (if empty) or just strip whitespaces. - :param string: - :return: - """ - if is_str_empty(string): # check for empty string - return None +def trim_to_none(string: str | None) -> str | None: + """Trim the provided string to None (if empty) or just strip whitespaces.""" + if string and string.strip(): + return string.strip() - return string.strip() # strip and return + return None -def trim_to_empty(string: str) -> str: - """Trim the provided string to empty string (''/"") or just strip whitespaces. - :param string: - :return: - """ - if is_str_empty(string): # check for empty string - return "" +def trim_to_empty(string: str | None) -> str: + """Trim the provided string to empty string ('' or "") or just strip whitespaces.""" + if string and string.strip(): + return string.strip() - return string.strip() + return "" def filter_str(string): # todo: fix filtering for non-cyrillic symbols too (add them) @@ -110,7 +93,7 @@ def get_last_part_of_the_url(url: str) -> str: if not url: # fail-fast behaviour raise PyUtilitiesException("Specified empty URL!") - return url[url.rfind("/") + 1:] + return url[url.rfind("/") + 1 :] if __name__ == "__main__": diff --git a/src/pyutilities/web/web_client.py b/src/pyutilities/web/web_client.py index 8ede3c1..45b20ef 100644 --- a/src/pyutilities/web/web_client.py +++ b/src/pyutilities/web/web_client.py @@ -13,7 +13,7 @@ - (HTTP status codes) https://en.wikipedia.org/wiki/List_of_HTTP_status_codes Created: Dmitrii Gusev, 10.10.2022 - Modified: Dmitrii Gusev, 22.11.2022 + Modified: Dmitrii Gusev, 25.11.2022 """ import logging @@ -27,9 +27,8 @@ from pyutilities.exception import PyUtilitiesException from pyutilities.defaults import MSG_MODULE_ISNT_RUNNABLE -# init module logger log = logging.getLogger(__name__) -log.debug(f"Logging for module {__name__} is configured.") +log.addHandler(logging.NullHandler()) HTTP_DEFAULT_TIMEOUT = 20 # default HTTP requests timeout (seconds) HTTP_DEFAULT_BACKOFF = 1 # default back off factor (it is better to not touch this value!) diff --git a/tests/utils/test_string_utils.py b/tests/utils/test_string_utils.py index e15e8e9..023e64a 100644 --- a/tests/utils/test_string_utils.py +++ b/tests/utils/test_string_utils.py @@ -5,13 +5,14 @@ Unit tests for strings module. Created: Dmitrii Gusev, 15.04.2019 - Modified: Dmitrii Gusev, 22.11.2022 + Modified: Dmitrii Gusev, 25.11.2022 """ import pytest import unittest -from pyutilities.utils.string_utils import filter_str -import pyutilities.utils.string_utils as pystr +from hypothesis import given +from hypothesis.strategies import characters, text +from pyutilities.utils.string_utils import trim_to_none, trim_to_empty, filter_str, process_url # common constants for testing EMPTY_STRINGS = ["", " ", None, "", " "] @@ -37,31 +38,22 @@ def tearDownClass(cls): # method just for the demo purpose pass - def test_is_str_empty_with_empty_strings(self): - for s in EMPTY_STRINGS: - self.assertTrue(pystr.is_str_empty(s), "Must be True!") - - def test_is_str_empty_with_non_empty_strings(self): - for k, v in NON_EMPTY_STRINGS.items(): - self.assertFalse(pystr.is_str_empty(k), "Must be False!") - self.assertFalse(pystr.is_str_empty(v), "Must be False!") - def test_trim_to_none_with_empty_strings(self): for s in EMPTY_STRINGS: - self.assertIsNone(pystr.trim_to_none(s), "Must be NoNe!") + self.assertIsNone(trim_to_none(s), "Must be NoNe!") def test_trim_to_none_with_non_empty_strings(self): for k, v in NON_EMPTY_STRINGS.items(): - self.assertEqual(k, pystr.trim_to_none(v), "Must be equals!") + self.assertEqual(k, trim_to_none(v), "Must be equals!") def test_trim_to_empty_with_empty_strings(self): for s in EMPTY_STRINGS: - self.assertEqual("", pystr.trim_to_empty(s), "Must be an empty string!") - self.assertEqual("", pystr.trim_to_empty(s), "Must be an empty string!") + self.assertEqual("", trim_to_empty(s), "Must be an empty string!") + self.assertEqual("", trim_to_empty(s), "Must be an empty string!") def test_trim_to_empty_with_non_empty_strings(self): for k, v in NON_EMPTY_STRINGS.items(): - self.assertEqual(k, pystr.trim_to_empty(v), "Must be equals!") + self.assertEqual(k, trim_to_empty(v), "Must be equals!") def test_filter_str_for_empty(self): for string in ["", " ", None]: @@ -105,4 +97,76 @@ def test_filter_str_for_string(self): ], ) def test_process_url(url, postfix, format_params, expected): - assert pystr.process_url(url, postfix, format_params) == expected + assert process_url(url, postfix, format_params) == expected + + +# todo: https://hypothesis.readthedocs.io/en/latest/data.html#hypothesis.strategies.text +# todo: https://en.wikipedia.org/wiki/Unicode_character_property +@given( + text( + alphabet=characters( + blacklist_categories=( + "Cc", + "Zs", + "Zl", + "Zp", + ) + ), + min_size=1, + max_size=100, + ) +) +def test_trim_to_none_with_meaningful_symbols(text): + assert trim_to_none(text) == text + + +@given( + text( + alphabet=characters( + whitelist_categories=( + "Zs", + "Zl", + "Zp", + ) + ), + min_size=1, + max_size=100, + ) +) +def test_trim_to_none_with_only_non_meaningful_symbols(text): + assert trim_to_none(text) is None + + +@given( + text( + alphabet=characters( + blacklist_categories=( + "Cc", + "Zs", + "Zl", + "Zp", + ) + ), + min_size=1, + max_size=100, + ) +) +def test_trim_to_empty_with_meaningful_symbols(text): + assert trim_to_empty(text) == text + + +@given( + text( + alphabet=characters( + whitelist_categories=( + "Zs", + "Zl", + "Zp", + ) + ), + min_size=1, + max_size=100, + ) +) +def test_trim_to_empty_with_only_non_meaningful_symbols(text): + assert trim_to_empty(text) == ""