-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #33 from vreuter/tests_and_docs
Test and logging infrastructure
- Loading branch information
Showing
25 changed files
with
2,465 additions
and
1,666 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
pandas | ||
pyyaml | ||
pytest==3.0.6 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
"""Project configuration, particularly for logging. | ||
Project-scope constants may reside here, but more importantly, some setup here | ||
will provide a logging infrastructure for all of the project's modules. | ||
Individual modules and classes may provide separate configuration on a more | ||
local level, but this will at least provide a foundation. | ||
""" | ||
|
||
import logging | ||
import os | ||
from sys import stderr | ||
|
||
|
||
LOOPERENV_VARNAME = "LOOPERENV" | ||
SUBMISSION_TEMPLATES_FOLDER = "submit_templates" | ||
DEFAULT_LOOPERENV_FILENAME = "default_looperenv.yaml" | ||
DEFAULT_LOOPERENV_CONFIG_RELATIVE = os.path.join(SUBMISSION_TEMPLATES_FOLDER, | ||
DEFAULT_LOOPERENV_FILENAME) | ||
|
||
LOGGING_LEVEL = logging.INFO | ||
LOGGING_LOCATIONS = (stderr, ) | ||
DEFAULT_LOGGING_FMT = "%(asctime)s %(name)s %(module)s : %(lineno)d - [%(levelname)s] > %(message)s" | ||
|
||
|
||
LOOPER_LOGGER = None | ||
|
||
|
||
def setup_looper_logger(level, additional_locations=None, | ||
fmt=None, datefmt=None): | ||
""" | ||
Called by test configuration via `pytest`'s `conftest`. | ||
All arguments are optional and have suitable defaults. | ||
:param int | str level: logging level | ||
:param tuple(str | FileIO[str]) additional_locations: supplementary | ||
destination(s) to which to ship logs | ||
:param str fmt: message format string for log message, optional | ||
:param str datefmt: datetime format string for log message time, optional | ||
""" | ||
|
||
# Establish the logger. | ||
global LOOPER_LOGGER | ||
LOOPER_LOGGER = logging.getLogger(__name__.split(".")[0]) | ||
LOOPER_LOGGER.handlers = [] | ||
try: | ||
LOOPER_LOGGER.setLevel(level) | ||
except Exception: | ||
logging.error("Can's set logging level to %s; using %s", | ||
str(LOGGING_LEVEL)) | ||
level = LOGGING_LEVEL | ||
LOOPER_LOGGER.setLevel(level) | ||
|
||
# Process any additional locations. | ||
locations_exception = None | ||
where = LOGGING_LOCATIONS | ||
if additional_locations: | ||
if isinstance(additional_locations, str): | ||
additional_locations = (additional_locations, ) | ||
try: | ||
where = LOGGING_LOCATIONS + tuple(additional_locations) | ||
except TypeError as e: | ||
locations_exception = e | ||
if locations_exception: | ||
logging.warn("Could not interpret {} as supplementary root logger " | ||
"target destinations; using {} as root logger location(s)". | ||
format(additional_locations, LOGGING_LOCATIONS)) | ||
|
||
# Add the handlers. | ||
formatter = logging.Formatter(fmt or DEFAULT_LOGGING_FMT, datefmt) | ||
for loc in where: | ||
if isinstance(loc, str): | ||
# File destination | ||
dirpath = os.path.dirname(loc) | ||
if not os.path.exists(dirpath): | ||
os.makedirs(dirpath) | ||
handler_type = logging.FileHandler | ||
elif hasattr(loc, "write"): | ||
# Stream destination | ||
handler_type = logging.StreamHandler | ||
else: | ||
# Strange supplementary destination | ||
logging.warn("{} as logs destination appears to be neither " | ||
"a filepath nor a stream.".format(loc)) | ||
continue | ||
handler = handler_type(loc) | ||
handler.setLevel(level) | ||
handler.setFormatter(formatter) | ||
LOOPER_LOGGER.addHandler(handler) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
""" Exceptions for specific looper issues. """ | ||
|
||
|
||
# Simplify imports, especially for models. | ||
__all__ = ["LooperConstructionException", "ProjectConstructionException", | ||
"DefaultLooperenvException", "ComputeEstablishmentException"] | ||
|
||
|
||
class LooperConstructionException(Exception): | ||
""" Error during construction of a looper ADT instance. """ | ||
def __init__(self, datatype, stage="", context=""): | ||
""" | ||
Explain failure during creation of `datatype` instance, with | ||
coarse contextual information given by `stage` and fine context | ||
given by `context`. | ||
:param str | type datatype: name of ADT under construction at | ||
time of error, or the type itself | ||
:param str stage: stage of the construction, optional | ||
:param str context: contextual information within stage, optional | ||
""" | ||
filler = "unspecified" | ||
if isinstance(datatype, str): | ||
typename = datatype | ||
else: | ||
try: | ||
typename = datatype.__name__ | ||
except AttributeError: | ||
typename = str(datatype) | ||
explanation = "Error creating {dt}; stage: {s}; context: {c}".\ | ||
format(dt=typename, s=stage or filler, c=context or filler) | ||
super(LooperConstructionException, self).__init__(explanation) | ||
|
||
|
||
class ProjectConstructionException(LooperConstructionException): | ||
""" An error occurred during attempt to instantiate `Project`. """ | ||
def __init__(self, reason, stage=""): | ||
""" | ||
Explain exception during `looper` `Project` construction. | ||
:param str reason: fine-grained contextual information | ||
:param str stage: broad phase of construction during which the | ||
error occurred | ||
""" | ||
super(ProjectConstructionException, self).__init__( | ||
datatype="Project", stage=stage, context=reason) | ||
|
||
|
||
class DefaultLooperenvException(ProjectConstructionException): | ||
""" Default looperenv setup call failed to | ||
set relevant `Project` attributes. """ | ||
def __init__(self, reason="Could not establish default looperenv"): | ||
super(DefaultLooperenvException, self).__init__(reason=reason) | ||
|
||
|
||
class ComputeEstablishmentException(ProjectConstructionException): | ||
""" Failure to establish `Project` `compute` setting(s). """ | ||
def __init__(self, reason="Could not establish Project compute env."): | ||
super(ComputeEstablishmentException, self).__init__(reason=reason) |
Oops, something went wrong.