From 128e6665e9dfc5dc59d77050ef9f88c14f43e40c Mon Sep 17 00:00:00 2001 From: BJ Dierkes Date: Sat, 22 Jun 2024 16:06:15 -0500 Subject: [PATCH] Type Annotations: utils.misc - Closes Issue #689 - Related to PR #628 --- CHANGELOG.md | 3 + cement/utils/misc.py | 224 ++++++++++++++++++++++++------------------- 2 files changed, 128 insertions(+), 99 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d21d870..3072b58e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,9 @@ Refactoring: - `[utils.fs]` Type Annotations - [Issue #688](https://github.com/datafolklabs/cement/issues/688) - [PR #628](https://github.com/datafolklabs/cement/pull/628) +- `[utils.misc]` Type Annotations + - [Issue #689](https://github.com/datafolklabs/cement/issues/689) + - [PR #628](https://github.com/datafolklabs/cement/pull/628) Misc: diff --git a/cement/utils/misc.py b/cement/utils/misc.py index c1427bf4..c829556f 100644 --- a/cement/utils/misc.py +++ b/cement/utils/misc.py @@ -6,10 +6,11 @@ import logging import hashlib from textwrap import TextWrapper from random import random +from typing import Any, Optional, Dict from ..core.deprecations import deprecate -def rando(salt=None): +def rando(salt: Optional[str] = None) -> str: """ Generate a random hash for whatever purpose. Useful for testing or any other time that something random is required. @@ -32,16 +33,111 @@ def rando(salt=None): """ if salt is None: - salt = random() + salt = str(random()) # issue-626: Use sha256 for compatibility with Redhat/FIPS restricted # policies. Return only 32 chars for backward compat with previous md5 - return hashlib.sha256(str(salt).encode()).hexdigest()[:32] + return hashlib.sha256(salt.encode()).hexdigest()[:32] + + +def init_defaults(*sections: str) -> Dict[str, Any]: + """ + Returns a standard dictionary object to use for application defaults. + If sections are given, it will create a nested dict for each section name. + This is sometimes more useful, or cleaner than creating an entire dict set + (often used in testing). + + Args: + sections: Section keys to create nested dictionaries for. + + Returns: + dict: Dictionary of nested dictionaries (sections) + + Example: + + .. code-block:: python + + from cement import App, init_defaults + + defaults = init_defaults('myapp', 'section2', 'section3') + defaults['myapp']['debug'] = False + defaults['section2']['foo'] = 'bar + defaults['section3']['foo2'] = 'bar2' + + app = App('myapp', config_defaults=defaults) + + """ + defaults: Dict[str, Any] = dict() + for section in sections: + defaults[section] = dict() + return defaults + + +def is_true(item: Any) -> bool: + """ + Given a value, determine if it is one of + ``[True, 'true', 'yes', 'y', 'on', '1', 1,]`` (note: strings are converted + to lowercase before comparison). + + Args: + item: The item to convert to a boolean. + + Returns: + bool: ``True`` if ``item`` equates to a true-ish value, ``False`` + otherwise + + """ + tstrings = ['true', 'yes', 'y', 'on', '1'] + if isinstance(item, str) and item.lower() in tstrings: + return True + elif isinstance(item, bool) and item is True: + return True + elif isinstance(item, int) and item == 1: + return True + else: + return False + + +def wrap(text: str, + width: int = 77, + indent: str = '', + long_words: bool = False, + hyphens: bool = False) -> str: + """ + Wrap text for cleaner output (this is a simple wrapper around + ``textwrap.TextWrapper`` in the standard library). + + Args: + text (str): The text to wrap + + Keyword Arguments: + width (int): The max width of a line before breaking + indent (str): String to prefix subsequent lines after breaking + long_words (bool): Whether or not to break on long words + hyphens (bool): Whether or not to break on hyphens + + Returns: + str: The wrapped string + + """ + + types = [str] + if type(text) not in types: + raise TypeError("Argument `text` must be one of [str, unicode].") + + wrapper = TextWrapper(subsequent_indent=indent, width=width, + break_long_words=long_words, + break_on_hyphens=hyphens) + return wrapper.fill(text) class MinimalLogger(object): - def __init__(self, namespace, debug, *args, **kw): + def __init__(self, + namespace: str, + debug: bool, + *args: Any, + **kw: Any) -> None: self.namespace = namespace self.backend = logging.getLogger(namespace) self._debug = debug @@ -70,7 +166,9 @@ class MinimalLogger(object): self.backend.addHandler(console) - def _get_logging_kwargs(self, namespace, **kw): + def _get_logging_kwargs(self, + namespace: Optional[str], + **kw: Any) -> Dict[Any, Any]: if not namespace: namespace = self.namespace @@ -84,7 +182,7 @@ class MinimalLogger(object): return kw @property - def logging_is_enabled(self): + def logging_is_enabled(self) -> bool: enabled = False if '--debug' in sys.argv or self._debug: deprecate('3.0.8-2') @@ -99,66 +197,48 @@ class MinimalLogger(object): return enabled - def info(self, msg, namespace=None, **kw): + def info(self, + msg: str, + namespace: Optional[str] = None, + **kw: Any) -> None: if self.logging_is_enabled: kwargs = self._get_logging_kwargs(namespace, **kw) self.backend.info(msg, **kwargs) - def warning(self, msg, namespace=None, **kw): + def warning(self, + msg: str, + namespace: Optional[str] = None, + **kw: Any) -> None: if self.logging_is_enabled: kwargs = self._get_logging_kwargs(namespace, **kw) self.backend.warning(msg, **kwargs) - def error(self, msg, namespace=None, **kw): + def error(self, + msg: str, + namespace: Optional[str] = None, + **kw: Any) -> None: if self.logging_is_enabled: kwargs = self._get_logging_kwargs(namespace, **kw) self.backend.error(msg, **kwargs) - def fatal(self, msg, namespace=None, **kw): + def fatal(self, + msg: str, + namespace: Optional[str] = None, + **kw: Any) -> None: if self.logging_is_enabled: kwargs = self._get_logging_kwargs(namespace, **kw) self.backend.fatal(msg, **kwargs) - def debug(self, msg, namespace=None, **kw): + def debug(self, + msg: str, + namespace: Optional[str] = None, + **kw: Any) -> None: if self.logging_is_enabled: kwargs = self._get_logging_kwargs(namespace, **kw) self.backend.debug(msg, **kwargs) -def init_defaults(*sections): - """ - Returns a standard dictionary object to use for application defaults. - If sections are given, it will create a nested dict for each section name. - This is sometimes more useful, or cleaner than creating an entire dict set - (often used in testing). - - Args: - sections: Section keys to create nested dictionaries for. - - Returns: - dict: Dictionary of nested dictionaries (sections) - - Example: - - .. code-block:: python - - from cement import App, init_defaults - - defaults = init_defaults('myapp', 'section2', 'section3') - defaults['myapp']['debug'] = False - defaults['section2']['foo'] = 'bar - defaults['section3']['foo2'] = 'bar2' - - app = App('myapp', config_defaults=defaults) - - """ - defaults = dict() - for section in sections: - defaults[section] = dict() - return defaults - - -def minimal_logger(namespace, debug=False): +def minimal_logger(namespace: str, debug: bool = False) -> MinimalLogger: """ Setup just enough for cement to be able to do debug logging. This is the logger used by the Cement framework, which is setup and accessed before @@ -185,57 +265,3 @@ def minimal_logger(namespace, debug=False): """ return MinimalLogger(namespace, debug) - - -def is_true(item): - """ - Given a value, determine if it is one of - ``[True, 'true', 'yes', 'y', 'on', '1', 1,]`` (note: strings are converted - to lowercase before comparison). - - Args: - item: The item to convert to a boolean. - - Returns: - bool: ``True`` if ``item`` equates to a true-ish value, ``False`` - otherwise - - """ - tstrings = ['true', 'yes', 'y', 'on', '1'] - if isinstance(item, str) and item.lower() in tstrings: - return True - elif isinstance(item, bool) and item is True: - return True - elif isinstance(item, int) and item == 1: - return True - else: - return False - - -def wrap(text, width=77, indent='', long_words=False, hyphens=False): - """ - Wrap text for cleaner output (this is a simple wrapper around - ``textwrap.TextWrapper`` in the standard library). - - Args: - text (str): The text to wrap - - Keyword Arguments: - width (int): The max width of a line before breaking - indent (str): String to prefix subsequent lines after breaking - long_words (bool): Whether or not to break on long words - hyphens (bool): Whether or not to break on hyphens - - Returns: - str: The wrapped string - - """ - - types = [str] - if type(text) not in types: - raise TypeError("Argument `text` must be one of [str, unicode].") - - wrapper = TextWrapper(subsequent_indent=indent, width=width, - break_long_words=long_words, - break_on_hyphens=hyphens) - return wrapper.fill(text)