i3pystatus/i3pystatus/core/util.py
2013-02-26 23:00:03 +01:00

167 lines
5.3 KiB
Python

import inspect
import types
import collections
from .exceptions import *
__all__ = [
"SettingsBase",
"ClassFinder",
"ModuleList",
"KeyConstraintDict", "PrefixedKeyDict",
]
def round_dict(dic, places):
for key, value in dic.items():
dic[key] = round(value, places)
class ModuleList(collections.UserList):
def __init__(self, status_handler, module_base):
self.status_handler = status_handler
self.finder = ClassFinder(module_base)
super().__init__()
def append(self, module, *args, **kwargs):
module = self.finder.instanciate_class_from_module(module, *args, **kwargs)
module.registered(self.status_handler)
super().append(module)
class PrefixedKeyDict(collections.UserDict):
def __init__(self, prefix):
super().__init__()
self.prefix = prefix
def __setitem__(self, key, value):
super().__setitem__(self.prefix + key, value)
class KeyConstraintDict(collections.UserDict):
class MissingKeys(Exception):
def __init__(self, keys):
self.keys = keys
def __init__(self, valid_keys, required_keys):
super().__init__()
self.valid_keys = valid_keys
self.required_keys = set(required_keys)
self.seen_keys = set()
def __setitem__(self, key, value):
if key in self.valid_keys:
self.seen_keys.add(key)
self.data[key] = value
else:
raise KeyError(key)
def __iter__(self):
if self.missing():
raise self.MissingKeys(self.missing())
return self.data.__iter__()
def missing(self):
return self.required_keys - (self.seen_keys & self.required_keys)
class SettingsBase:
"""
Support class for providing a nice and flexible settings interface
Classes inherit from this class and define what settings they provide and
which are required.
The constructor is either passed a dictionary containing these settings, or
keyword arguments specifying the same.
Settings are stored as attributes of self
"""
settings = tuple()
"""settings should be tuple containing two types of elements:
* bare strings, which must be valid identifiers.
* two-tuples, the first element being a identifier (as above) and the second
a docstring for the particular setting"""
required = tuple()
"""required can list settings which are required"""
def __init__(self, *args, **kwargs):
def flatten_setting(setting):
return setting[0] if isinstance(setting, tuple) else setting
def flatten_settings(settings):
return tuple(flatten_setting(setting) for setting in settings)
def get_argument_dict(args, kwargs):
if len(args) == 1 and not kwargs:
# User can also pass in a dict for their settings
# Note: you could do that anyway, with the ** syntax
return args[0]
return kwargs
self.settings = flatten_settings(self.settings)
sm = KeyConstraintDict(self.settings, self.required)
settings_source = get_argument_dict(args, kwargs)
try:
sm.update(settings_source)
except KeyError as exc:
raise ConfigKeyError(type(self).__name__, key=exc.args[0]) from exc
try:
self.__dict__.update(sm)
except KeyConstraintDict.MissingKeys as exc:
raise ConfigMissingError(type(self).__name__, missing=exc.keys) from exc
self.__name__ = "{}.{}".format(self.__module__, self.__class__.__name__)
self.init()
def init(self):
"""Convenience method which is called after all settings are set
In case you don't want to type that super()…blabla :-)"""
class ClassFinder:
"""Support class to find classes of specific bases in a module"""
def __init__(self, baseclass):
self.baseclass = baseclass
def predicate_factory(self, module):
def predicate(obj):
return (
inspect.isclass(obj) and
issubclass(obj, self.baseclass) and
obj.__module__ == module.__name__
)
return predicate
def search_module(self, module):
return list(zip(*inspect.getmembers(module, self.predicate_factory(module))))[1]
def get_class(self, module):
classes = self.search_module(module)
if len(classes) > 1:
# If there are multiple Module clases bundled in one module,
# well, we can't decide for the user.
raise ConfigAmbigiousClassesError(module.__name__, classes)
elif not classes:
raise ConfigInvalidModuleError(module.__name__)
return classes[0]
def get_module(self, module):
return getattr(__import__("i3pystatus.{module}".format(module=module), globals(), {}, []), module)
def instanciate_class_from_module(self, module, *args, **kwargs):
if isinstance(module, types.ModuleType):
return self.get_class(module)(*args, **kwargs)
elif isinstance(module, str):
return self.instanciate_class_from_module(self.get_module(module), *args, **kwargs)
elif args or kwargs:
raise ValueError("Additional arguments are invalid if 'module' is already an object")
return module