Skip to content

Commit

Permalink
Merge pull request #11 from mvinyard/dev
Browse files Browse the repository at this point in the history
Add Comprehensive Logging System to ABCParse
  • Loading branch information
mvinyard authored Feb 28, 2025
2 parents 381f488 + 32e5404 commit 504cce2
Show file tree
Hide file tree
Showing 20 changed files with 1,013 additions and 174 deletions.
18 changes: 13 additions & 5 deletions .github/workflows/python-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,30 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.7, 3.8, 3.9]
python-version: [3.8, 3.9, '3.10', '3.11']

steps:
- name: Check out repository
uses: actions/checkout@v2
uses: actions/checkout@v3

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install -r test-requirements.txt
pip install -e .
- name: Run tests
- name: Run tests with coverage
run: |
python -m unittest discover
pytest tests/test_ABCParse.py tests/test_logging.py --cov=ABCParse --cov-report=xml
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
file: ./coverage.xml
fail_ci_if_error: false
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ __pycache__/
# C extensions
*.so

.pytest_cache/
.log_cache/

# Distribution / packaging
.Python
build/
Expand Down
14 changes: 12 additions & 2 deletions ABCParse/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# __init__.py

from ._abc_parse import ABCParse
from ._function_kwargs import function_kwargs
from ._as_list import as_list
from . import logging

from .__version__ import __version__


__all__ = [
"ABCParse",
"function_kwargs",
"as_list",
"logging",
"__version__",
]
2 changes: 1 addition & 1 deletion ABCParse/__version__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.0.8"
__version__ = "0.1.0"
57 changes: 36 additions & 21 deletions ABCParse/_abc_parse.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

__module_name__ = "_abc_parse.py"
__doc__ = """Better abstract base class for auto-parsing inputs."""
__author__ = ", ".join(["Michael E. Vinyard"])
Expand All @@ -8,20 +7,17 @@
# -- import packages: ---------------------------------------------------------
import abc


# -- import local dependencies: -----------------------------------------------
from ._info_message import InfoMessage


# -- set typing: ---------------------------------------------------------------
# -- set type hints: ----------------------------------------------------------
from typing import Any, Dict, List, Optional, Tuple

# -- import internal modules: -------------------------------------------------
from . import logging

# -- Controller class: ---------------------------------------------------------
# -- Controller class: --------------------------------------------------------
class ABCParse(abc.ABC):
_BUILT = False

def __init__(self, *args, **kwargs):
def __init__(self, *args, **kwargs) -> None:
"""
we avoid defining things in __init__ because this subsequently
mandates the use of `super().__init__()`
Expand All @@ -45,16 +41,25 @@ def __call__(self, x=4, y=5, z=3, *args, **kwargs):
dc._PARAMS
```
"""

pass

def __build__(self) -> None:
...

def _initialize_logger(self, level: str = "warning", file_path: str = logging._format.DEFAULT_LOG_FILEPATH) -> None:
# Initialize logger with class name and logging parameters
self._logger = logging.get_logger(
name=self.__class__.__name__,
level=level,
file_path=file_path
)
self._logger.debug(f"Initializing {self.__class__.__name__}")

def __build__(self, level: str = "warning", file_path: str = logging._format.DEFAULT_LOG_FILEPATH) -> None:
self._PARAMS = {}
self._IGNORE = ["self", "__class__"]
self._stored_private = []
self._stored_public = []

self._initialize_logger(level, file_path)
self._BUILT = True
self._logger.debug("Built internal structures")

def __set__(
self, key: str, val: Any, public: List = [], private: List = []
Expand All @@ -64,8 +69,10 @@ def __set__(
if (key in private) and (not key in public):
self._stored_private.append(key)
key = f"_{key}"
self._logger.debug(f"Setting private attribute: {key}")
else:
self._stored_public.append(key)
self._logger.debug(f"Setting public attribute: {key}")
setattr(self, key, val)

def __set_existing__(self, key: str, val: Any) -> None:
Expand All @@ -80,16 +87,19 @@ def __set_existing__(self, key: str, val: Any) -> None:
attr.update(val)
setattr(self, key, attr)
self._PARAMS.update(val)
self._logger.debug(f"Updated kwargs: {val}")

elif passed_key == "args":
attr = getattr(self, key)
attr += val
setattr(self, key, attr)
self._PARAMS[passed_key] += val
self._logger.debug(f"Updated args: {val}")

else:
self._PARAMS[passed_key] = val
setattr(self, key, val)
self._logger.debug(f"Updated attribute {key}: {val}")

@property
def _STORED(self) -> List:
Expand All @@ -100,9 +110,11 @@ def __setup_inputs__(self, kwargs, public, private, ignore) -> Tuple[List]:
self.__build__()

self._IGNORE += ignore
self._logger.debug(f"Setup inputs with ignore list: {self._IGNORE}")

if len(public) > 0:
private = list(kwargs.keys())
self._logger.debug(f"Public attributes specified, setting all others as private")

return public, private

Expand All @@ -112,8 +124,6 @@ def __parse__(
public: Optional[List] = [None],
private: Optional[List] = [],
ignore: Optional[List] = [],
INFO: str = "INFO",
color: str = "BLUE",
) -> None:
"""
Made to be called during `cls.__init__` of the inherited class.
Expand All @@ -132,15 +142,13 @@ def __parse__(
"""

public, private = self.__setup_inputs__(kwargs, public, private, ignore)
self._logger.debug(f"Parsing kwargs: {kwargs}")

for key, val in kwargs.items():
if not key in self._IGNORE:
self.__set__(key, val, public, private)

self._INFO = InfoMessage(INFO = INFO, color = color)

def _update_info_msg(self, **kwargs):
self._INFO = self.InfoMessage(**kwargs)
self._logger.info(f"Parsed {len(self._PARAMS)} parameters")

def __update__(
self,
Expand Down Expand Up @@ -171,12 +179,19 @@ def __update__(
-------
None
"""

self._logger.debug(f"Updating with kwargs: {kwargs}")
public, private = self.__setup_inputs__(kwargs, public, private, ignore)

updated_count = 0
new_count = 0

for key, val in kwargs.items():
if not (val is None) and (key in self._STORED):
self.__set_existing__(key, val)
updated_count += 1

elif not (val is None) and not (key in self._IGNORE):
self.__set__(key, val, public, private)
new_count += 1

self._logger.info(f"Updated {updated_count} existing parameters and added {new_count} new parameters")
64 changes: 48 additions & 16 deletions ABCParse/_as_list.py
Original file line number Diff line number Diff line change
@@ -1,50 +1,73 @@

# -- set typing: --------------------------------------------------------------
# -- set type hints: ----------------------------------------------------------
from typing import Any, List, Optional, Union

# -- import internal modules: -------------------------------------------------
from . import logging

# -- module logger: -----------------------------------------------------------
_logger = logging.get_logger(name="as_list")

# -- controller class: --------------------------------------------------------
class AsList(object):
"""Enables flexible inputs as list with type-checking."""

def __init__(self, *args, **kwargs):

""" """
...
def __init__(self, *args, **kwargs) -> None:
"""
Parameters
----------
*args
type: Any
**kwargs
"""
self._logger = logging.get_logger(name="AsList", level="warning")
self._logger.debug("Initializing AsList")

@property
def is_list(self) -> bool:
return isinstance(self._input, List)

@property
def _MULTIPLE_TARGET_TYPES(self):
def _MULTIPLE_TARGET_TYPES(self) -> bool:
return isinstance(self._target_type, List)

def _is_target_type(self, value) -> bool:
if self._MULTIPLE_TARGET_TYPES:
return any([isinstance(value, target_type) for target_type in self._target_type])
return isinstance(value, self._target_type)
result = any([isinstance(value, target_type) for target_type in self._target_type])
if not result:
self._logger.debug(f"Value {value} does not match any of the target types: {self._target_type}")
return result
result = isinstance(value, self._target_type)
if not result:
self._logger.debug(f"Value {value} is not of target type: {self._target_type}")
return result

def _as_list(self) -> List:
def _as_list(self) -> List[Any]:
if not self.is_list:
self._logger.debug(f"Converting single value to list: {self._input}")
return [self._input]
return self._input

@property
def list_values(self) -> List:
def list_values(self) -> List[Any]:
return self._as_list()

@property
def validated_target_types(self) -> bool:
return all([self._is_target_type(val) for val in self.list_values])
result = all([self._is_target_type(val) for val in self.list_values])
if result:
self._logger.debug("All values match target type(s)")
else:
self._logger.warning("Not all values match target type(s)")
return result

def __call__(
self,
input: Union[List[Any], Any],
target_type: Optional[Union[type, List[type]]] = None,
*args,
**kwargs,
):
) -> List[Any]:
"""
Parameters
----------
Expand All @@ -60,16 +83,22 @@ def __call__(
self._input = input
self._target_type = target_type

self._logger.debug(f"Processing input: {input}, target_type: {target_type}")

if not self._target_type is None:
assert self.validated_target_types, "Not all values match the target type"
self._logger.info(f"Validated {len(self.list_values)} values against target type(s)")

return self.list_values


# -- API-facing function: -----------------------------------------------------
def as_list(
input: Union[List[Any], Any], target_type: Optional[Union[type, List[type]]] = None, *args, **kwargs,
):
input: Union[List[Any], Any],
target_type: Optional[Union[type, List[type]]] = None,
*args,
**kwargs,
) -> List[Any]:
"""
Pass input to type-consistent list.
Expand All @@ -84,5 +113,8 @@ def as_list(
-------
List[Any]
"""
_logger.debug(f"as_list called with input: {input}, target_type: {target_type}")
_as_list = AsList()
return _as_list(input=input, target_type=target_type)
result = _as_list(input=input, target_type=target_type, *args, **kwargs)
_logger.info(f"Converted to list with {len(result)} elements")
return result
Loading

0 comments on commit 504cce2

Please sign in to comment.