From 03d96ad0ea8a1f26111c955ba303a10a31c4d2f1 Mon Sep 17 00:00:00 2001 From: enkore Date: Tue, 5 Mar 2013 17:26:10 +0100 Subject: [PATCH] Some internal code butchering again. --- i3pystatus/core/imputil.py | 44 +++++++++++++ i3pystatus/core/modules.py | 2 +- i3pystatus/core/settings.py | 61 +++++++++++++++++ i3pystatus/core/threads.py | 102 ++++++++++++++--------------- i3pystatus/core/util.py | 127 ++++++------------------------------ i3pystatus/mkdocs.py | 42 +++++------- mkdocs.sh | 2 +- 7 files changed, 191 insertions(+), 189 deletions(-) create mode 100644 i3pystatus/core/imputil.py create mode 100644 i3pystatus/core/settings.py diff --git a/i3pystatus/core/imputil.py b/i3pystatus/core/imputil.py new file mode 100644 index 0000000..8bf3991 --- /dev/null +++ b/i3pystatus/core/imputil.py @@ -0,0 +1,44 @@ +import inspect +import types + +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 diff --git a/i3pystatus/core/modules.py b/i3pystatus/core/modules.py index 379a31b..a74da20 100644 --- a/i3pystatus/core/modules.py +++ b/i3pystatus/core/modules.py @@ -1,7 +1,7 @@ from threading import Thread import time -from .util import SettingsBase +from .settings import SettingsBase from .threads import AutomagicManager __all__ = [ diff --git a/i3pystatus/core/settings.py b/i3pystatus/core/settings.py new file mode 100644 index 0000000..d8db876 --- /dev/null +++ b/i3pystatus/core/settings.py @@ -0,0 +1,61 @@ +from .util import KeyConstraintDict +from .exceptions import ConfigKeyError, ConfigMissingError + +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 :-)""" diff --git a/i3pystatus/core/threads.py b/i3pystatus/core/threads.py index 487dc7f..1efa635 100644 --- a/i3pystatus/core/threads.py +++ b/i3pystatus/core/threads.py @@ -1,9 +1,9 @@ - import sys import threading import time import traceback -import collections + +from .util import partition try: from setproctitle import setproctitle @@ -11,74 +11,76 @@ except ImportError: def setproctitle(title): pass -if hasattr(time, "perf_counter"): - timer = time.perf_counter -else: - timer = time.clock +timer = time.perf_counter if hasattr(time, "perf_counter") else time.clock -class ExceptionWrapper: +class Wrapper: def __init__(self, workload): self.workload = workload - def __call__(self): - try: - self.workload() - except Exception as exc: - traceback.print_exception(*sys.exc_info(), file=sys.stderr) - def __repr__(self): return repr(self.workload) -class WorkloadWrapper: - def __init__(self, workload): - self.workload = workload - self.time = 0.0 +class ExceptionWrapper(Wrapper): + def __call__(self): + try: + self.workload() + except Exception as exc: + sys.stderr.write("Exception in {thread}".format(thread=threading.current_thread().name)) + traceback.print_exception(*sys.exc_info(), file=sys.stderr) + sys.stderr.flush() + +class WorkloadWrapper(Wrapper): + time = 0.0 def __call__(self): tp1 = timer() self.workload() self.time = timer() - tp1 - def __repr__(self): - return repr(self.workload) - class Thread(threading.Thread): def __init__(self, target_interval, workloads=None, start_barrier=1): super().__init__() - - self.workloads = workloads if workloads is not None else [] + self.workloads = workloads or [] self.target_interval = target_interval self.start_barrier = start_barrier self.daemon = True def __iter__(self): return iter(self.workloads) + def __len__(self): return len(self.workloads) + def pop(self): return self.workloads.pop() + def append(self, workload): self.workloads.append(workload) - def run(self): - while len(self) <= self.start_barrier: - time.sleep(0.3) - - setproctitle("i3pystatus: {name}/{workloads}".format(name=self.name, workloads=list(map(repr, self.workloads)))) - - while self: - for workload in self: - workload() - self.workloads.sort(key=lambda workload: workload.time) - - filltime = self.target_interval - self.time - if filltime > 0: - time.sleep(filltime) - @property def time(self): return sum(map(lambda workload: workload.time, self)) + def wait_for_start_barrier(self): + while len(self) <= self.start_barrier: + time.sleep(0.4) + + def setproctitle(self): + setproctitle("i3pystatus {name}: {workloads}".format(name=self.name, workloads=list(map(repr, self.workloads)))) + + def execute_workloads(self): + for workload in self: + workload() + self.workloads.sort(key=lambda workload: workload.time) + + def run(self): + self.setproctitle() + while self: + self.execute_workloads() + filltime = self.target_interval - self.time + if filltime > 0: + time.sleep(filltime) + class AutomagicManager: def __init__(self, target_interval): self.target_interval = target_interval @@ -91,33 +93,27 @@ class AutomagicManager: def __call__(self): separate = [] - for thread in self.threads: - separate.extend(self.optimize(thread, thread.time)) - + separate.extend(self.branch(thread, thread.time)) self.create_threads(self.partition(separate)) + def __repr__(self): + return "Manager" + def wrap(self, workload): return WorkloadWrapper(ExceptionWrapper(workload)) - def optimize(self, thread, vtime): +# def calculate_sparse_times(): +# return ((self.lower_bound - thread.time, thread) for thread in self.threads) + + def branch(self, thread, vtime): if len(thread) > 1 and vtime > self.upper_bound: remove = thread.pop() - return [remove] + self.optimize(thread, vtime - remove.time) + return [remove] + self.branch(thread, vtime - remove.time) return [] def partition(self, workloads): - timesum = 0.0 - new_threads = [] - current_thread = [] - for workload in workloads: - current_thread.append(workload) - timesum += workload.time - if timesum > self.lower_bound: - new_threads.append(current_thread) - current_thread = [] - timesum = 0 - return new_threads + return partition(workloads, self.lower_bound, lambda workload: workload.time) def create_threads(self, threads): for workloads in threads: diff --git a/i3pystatus/core/util.py b/i3pystatus/core/util.py index 84c3880..4c09115 100644 --- a/i3pystatus/core/util.py +++ b/i3pystatus/core/util.py @@ -1,17 +1,31 @@ -import inspect -import types import collections from .exceptions import * +from .imputil import ClassFinder __all__ = [ - "SettingsBase", - "ClassFinder", - "ModuleList", - "KeyConstraintDict", "PrefixedKeyDict", + "ModuleList", "KeyConstraintDict", "PrefixedKeyDict", ] +def popwhile(predicate, iterable): + while iterable: + item = iterable.pop() + if predicate(item): + yield item + else: + break + +def partition(iterable, limit, key=None): + key = key or (lambda x: x) + partitions = [] + while iterable: + sum = 0.0 + partitions.append(list( + popwhile(lambda x: sum + key(x) or sum < limit, iterable) + )) + return partitions + def round_dict(dic, places): for key, value in dic.items(): dic[key] = round(value, places) @@ -63,104 +77,3 @@ class KeyConstraintDict(collections.UserDict): 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 diff --git a/i3pystatus/mkdocs.py b/i3pystatus/mkdocs.py index 76f725c..1ed34c7 100755 --- a/i3pystatus/mkdocs.py +++ b/i3pystatus/mkdocs.py @@ -9,7 +9,7 @@ import textwrap import i3pystatus import i3pystatus.mail -from .core.util import ClassFinder +from .core.imputil import ClassFinder IGNORE = ("__main__", "mkdocs") MODULE_FORMAT = """ @@ -36,21 +36,16 @@ class Module: else: self.name = "{module}.{cls}".format(module=module_name, cls=self.cls.__name__) - if self.cls.__doc__ is not None: - self.doc = self.cls.__doc__ - elif module.__doc__ is not None: - self.doc = module.__doc__ - else: - self.doc = "" + self.doc = self.cls.__doc__ or module.__doc__ or "" if hasattr(self.cls, "_endstring"): self.endstring = self.cls._endstring - self.get_settings() + self.read_settings() - def get_settings(self): + def read_settings(self): for setting in self.cls.settings: - self.settings.append(Setting(self, setting)) + self.settings.append(Setting(self.cls, setting)) def format_settings(self): return "\n".join(map(str, self.settings)) @@ -65,22 +60,21 @@ class Module: ) class Setting: - name = "" doc = "" required = False default = sentinel = object() - def __init__(self, mod, setting): + def __init__(self, cls, setting): if isinstance(setting, tuple): self.name = setting[0] self.doc = setting[1] else: self.name = setting - if setting in mod.cls.required: + if setting in cls.required: self.required = True - elif hasattr(mod.cls, self.name): - self.default = getattr(mod.cls, self.name) + elif hasattr(cls, self.name): + self.default = getattr(cls, self.name) def __str__(self): attrs = [] @@ -99,15 +93,11 @@ class Setting: return formatted -def get_modules(path=None): - if path is None: - path = i3pystatus.get_path() - +def get_modules(path): modules = [] for finder, modname, ispkg in pkgutil.iter_modules(path): if modname not in IGNORE: modules.append(get_module(finder, modname)) - return modules def get_module(finder, modname): @@ -116,12 +106,11 @@ def get_module(finder, modname): def get_all(module_path, heading, finder=None): mods = [] - if finder is None: - finder = i3pystatus.ClassFinder(i3pystatus.Module) + if not finder: + finder = ClassFinder(i3pystatus.Module) for name, module in get_modules(module_path): classes = finder.search_module(module) - for cls in classes: mods.append(Module(cls, neighbours=len(classes), module_name=name, module=module, heading=heading)) @@ -131,10 +120,9 @@ def generate_doc_for_module(module_path, heading="###", finder=None): return "".join(map(str, get_all(module_path, heading, finder))) with open("README.tpl.md", "r") as template: - tpl = template.read() tpl = tpl.replace("!!module_doc!!", generate_doc_for_module(i3pystatus.__path__)) - finder = i3pystatus.ClassFinder(baseclass=i3pystatus.mail.Backend) + finder = ClassFinder(baseclass=i3pystatus.mail.Backend) tpl = tpl.replace("!!i3pystatus.mail!!", generate_doc_for_module(i3pystatus.mail.__path__, "###", finder).replace("\n", "\n> ")) - - print(tpl) + with open("README.md", "w") as output: + output.write(tpl + "\n") diff --git a/mkdocs.sh b/mkdocs.sh index a6ce5f1..aae78a3 100755 --- a/mkdocs.sh +++ b/mkdocs.sh @@ -1,4 +1,4 @@ #!/bin/sh -python -m i3pystatus.mkdocs > README.md +python -m i3pystatus.mkdocs