Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add global config #22

Open
wants to merge 10 commits into
base: develop
Choose a base branch
from
1 change: 1 addition & 0 deletions hatchet/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@

from .graphframe import GraphFrame
from .query import QueryMatcher
from .util.rcmanager import RcParams
125 changes: 125 additions & 0 deletions hatchet/tests/rctest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
from hatchet.util.rcmanager import (
RcManager,
ConfigValidator,
_resolve_conf_file,
_read_config_from_file,
)


def test_creation():
file = _resolve_conf_file()
config = _read_config_from_file(file)
RC = RcManager(config)

assert RC is not None


def test_global_binding():
import hatchet as ht

assert hasattr(ht, "RcParams")


def test_change_value():
import hatchet as ht
from hatchet.util.rcmanager import RcParams

assert "logging" in ht.RcParams

ht.RcParams["logging"] = True
assert ht.RcParams["logging"] is True
assert RcParams["logging"] is True

ht.RcParams["logging"] = False
assert ht.RcParams["logging"] is False
assert RcParams["logging"] is False


def test_validator():
V = ConfigValidator()

# Add validators for keys where we can input
# bad values
V._validations["bad_bool"] = V.bool_validator
V._validations["bad_string"] = V.str_validator
V._validations["bad_int"] = V.int_validator
V._validations["bad_float"] = V.float_validator
V._validations["bad_list"] = V.list_validator
V._validations["bad_dict"] = V.dict_validator

# Test passes if exception is thrown
# Else: test fails
try:
V.validate("bad_bool", "True")
assert False
except TypeError:
assert True

try:
V.validate("bad_string", True)
assert False
except TypeError:
assert True

try:
V.validate("bad_int", "123")
assert False
except TypeError:
assert True

try:
V.validate("bad_float", "1.2387")
assert False
except TypeError:
assert True

try:
V.validate("bad_list", {})
assert False
except TypeError:
assert True

try:
V.validate("bad_dict", [])
assert False
except TypeError:
assert True

# Testing valid inputs
# Goes through to true assertion if
# validation works
try:
V.validate("bad_bool", True)
assert True
except TypeError:
assert False

try:
V.validate("bad_string", "string")
assert True
except TypeError:
assert False

try:
V.validate("bad_int", 1)
assert True
except TypeError:
assert False

try:
V.validate("bad_float", 1.2387)
assert True
except TypeError:
assert False

try:
V.validate("bad_list", [])
assert True
except TypeError:
assert False

try:
V.validate("bad_dict", {})
assert True
except TypeError:
assert False
156 changes: 156 additions & 0 deletions hatchet/util/rcmanager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
# Copyright 2021 The University of Arizona and other Hatchet Project
# Developers. See the top-level LICENSE file for details.
#
# SPDX-License-Identifier: MIT

try:
from collections.abc import MutableMapping
except ImportError:
from collections import MutableMapping
import os.path as path
import yaml


class ConfigValidator:
def __init__(self):
self._validations = {}
self._set_validators_to_configs()

def bool_validator(self, key, value):
if not isinstance(value, bool):
raise TypeError(
'Error loading configuration: Configuration "{}" must be of type bool'.format(
key
)
)
else:
return value

def str_validator(self, key, value):
if not isinstance(value, str):
raise TypeError(
'Error loading configuration: Configuration "{}" must be of type string'.format(
key
)
)
else:
return value

def int_validator(self, key, value):
if not isinstance(value, int):
raise TypeError(
'Error loading configuration: Configuration "{}" must be of type int'.format(
key
)
)
else:
return value

def float_validator(self, key, value):
if not isinstance(value, float):
raise TypeError(
'Error loading configuration: Configuration "{}" must be of type float'.format(
key
)
)
else:
return value

def list_validator(self, key, value):
if not isinstance(value, list):
raise TypeError(
'Error loading configuration: Configuration "{}" must be of type list'.format(
key
)
)
else:
return value

def dict_validator(self, key, value):
if not isinstance(value, dict):
raise TypeError(
'Error loading configuration: Configuration "{}" must be of type dict'.format(
key
)
)
else:
return value

def validate(self, key, value):
return self._validations[key](key, value)

def _set_validators_to_configs(self):
self._validations["logging"] = self.bool_validator
self._validations["log_directory"] = self.str_validator
self._validations["highlight_name"] = self.bool_validator
self._validations["invert_colormap"] = self.bool_validator


class RcManager(MutableMapping):
"""
A runtime configurations class; modeled after the RcParams class in matplotlib.
The benifit of this class over a dictonary is validation of set item and formatting
of output.
"""

def __init__(self, *args, **kwargs):
self._data = {}
self._validator = ConfigValidator()
self._data.update(*args, **kwargs)

def __setitem__(self, key, val):
"""
Function loads valid configurations and prints errors for invalid configs.
"""
try:
self._validator.validate(key, val)
return self._data.__setitem__(key, val)
except TypeError as e:
raise e

def __getitem__(self, key):
return self._data[key]

def __iter__(self):
for kv in sorted(self._data.__iter__(self)):
yield kv

def __str__(self):
return "\n".join(map("{0[0]}: {0[1]}".format, sorted(self.items())))

def __len__(self):
return len(self._data)

def __delitem__(self, item):
del self._data[item]


def _resolve_conf_file():
"""
Determines which configuration file to load.
Uses the precendence order:
1. $HOME/.hatchet/hatchetrc.yaml
2. $HATCHET_BASE_DIR/hatchetrc.yaml
"""
home = path.expanduser("~")
conf_dir = path.join(home, ".hatchet", "hatchetrc.yaml")
if path.exists(conf_dir):
return conf_dir
else:
hatchet_path = path.abspath(path.dirname(path.abspath(__file__)))
rel_path = path.join(hatchet_path, "..", "..", "hatchetrc.yaml")
return rel_path


def _read_config_from_file(filepath):
configs = {}
with open(filepath, "r") as f:
configs = yaml.load(f, yaml.FullLoader)
return configs


filepath = _resolve_conf_file()
configs = _read_config_from_file(filepath)

# Global instance of configurations
RcParams = RcManager(configs)
30 changes: 30 additions & 0 deletions hatchetrc.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
##############################################
## ##
## Global configuration file for hatchet. ##
## ##
##############################################

## If you would like to customize these global configurations
## please copy it to the following directory and edit there:
## $HOME/.hatchet/
##
## This configurations file is considered in the following order of precedence:
## 1. $HOME/.hatchet/hatchetrc.yaml (optional)
## 2. $HATCHET_BASE_DIR/hatchetrc.yaml
##
## If no file can be found at $HOME/.hatchet then configurations are drawn from
## the hatchetrc.yaml file at the root of hatchet's installation location

##
## Logging configuration
##

logging: False
log_directory: "~"

##
## Tree Output Configuration
##

highlight_name: False
invert_colormap: False