Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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