Skip to content

Commit

Permalink
Merge pull request #33 from vreuter/tests_and_docs
Browse files Browse the repository at this point in the history
Test and logging infrastructure
  • Loading branch information
vreuter committed Feb 14, 2017
2 parents a2bbe11 + 8353576 commit 09a7195
Show file tree
Hide file tree
Showing 25 changed files with 2,465 additions and 1,666 deletions.
18 changes: 17 additions & 1 deletion .gitignore
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ tests/test/*
doc/build/*

# generic ignore list:
*.lst

# Compiled source
*.com
Expand Down Expand Up @@ -46,4 +47,19 @@ Thumbs.db
*~

# libreoffice lock files:
.~lock*
.~lock*

# Default-named test output
microtest/
open_pipelines/

# IDE-specific items
.idea/

# pytest-related
.cache/
.coverage

# Reserved files for comparison
*RESERVE*

Empty file modified README.md
100755 → 100644
Empty file.
3 changes: 3 additions & 0 deletions dev-requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pandas
pyyaml
pytest==3.0.6
2 changes: 1 addition & 1 deletion doc/source/advanced.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ This is common, and it works in a pinch with Looper, but what if the data get mo
"frog_0h", "RRBS", "frog", "0", "source1"
"frog_1h", "RRBS", "frog", "1", "source1"

Then, in your config file you specify which sample attributes (similar to the metadata columns) are derived (in this case, ``file_path``), as well as a string that will construct your path based on other sample attributes encoded using brackets as in ``{sample_attribute}``, like this:
Then, in your config file you specify which sample attributes (similar to the metadata columns) are derived (in this case, ``file_path``), as well as a string that will construct your path based on other sample attributes encoded using braces as in ``{sample_attribute}``, like this:


.. code-block:: yaml
Expand Down
89 changes: 89 additions & 0 deletions looper/__init__.py
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)
59 changes: 59 additions & 0 deletions looper/exceptions.py
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)

0 comments on commit 09a7195

Please sign in to comment.