From ffa9ab6bc82e86bc1d90e0fe3d2590e0af65b854 Mon Sep 17 00:00:00 2001 From: Romain Dorgueil Date: Mon, 1 May 2017 15:52:34 +0200 Subject: [PATCH] Fix positional options (#17) and associated tests. --- bonobo/config/configurables.py | 45 +++++++++-------------- bonobo/config/options.py | 7 +++- bonobo/io/file.py | 2 +- bonobo/util/objects.py | 1 + tests/{fixme_config.py => test_config.py} | 0 5 files changed, 25 insertions(+), 30 deletions(-) rename tests/{fixme_config.py => test_config.py} (100%) diff --git a/bonobo/config/configurables.py b/bonobo/config/configurables.py index 93259d1..bf6df76 100644 --- a/bonobo/config/configurables.py +++ b/bonobo/config/configurables.py @@ -5,29 +5,6 @@ __all__ = [ 'Option', ] -_options_insert_order = 0 - -class Option: - def __init__(self, type=None, *, required=False, positional=False, default=None): - self.name = None - self.type = type - self.required = required - self.positional = positional - self.default = default - - global _options_insert_order - self.order = _options_insert_order - _options_insert_order += 1 - - def __get__(self, inst, typ): - if not self.name in inst.__options_values__: - inst.__options_values__[self.name] = self.default() if callable(self.default) else self.default - return inst.__options_values__[self.name] - - def __set__(self, inst, value): - inst.__options_values__[self.name] = self.type(value) if self.type else value - - class ConfigurableMeta(type): """ Metaclass for Configurables that will add options to a special __options__ dict. @@ -36,6 +13,8 @@ class ConfigurableMeta(type): def __init__(cls, what, bases=None, dict=None): super().__init__(what, bases, dict) cls.__options__ = {} + cls.__positional_options__ = [] + for typ in cls.__mro__: for name, value in typ.__dict__.items(): if isinstance(value, Option): @@ -43,6 +22,8 @@ class ConfigurableMeta(type): value.name = name if not name in cls.__options__: cls.__options__[name] = value + if value.positional: + cls.__positional_options__.append(name) class Configurable(metaclass=ConfigurableMeta): @@ -55,16 +36,24 @@ class Configurable(metaclass=ConfigurableMeta): def __init__(self, *args, **kwargs): super().__init__() + # initialize option's value dictionary, used by descriptor implementation (see Option). self.__options_values__ = {} - missing = list() + # compute missing options, given the kwargs. + missing = set() for name, option in type(self).__options__.items(): if option.required and not option.name in kwargs: - missing.append(name) + missing.add(name) - for i in range(min(len(args), len(missing))): - kwargs + # transform positional arguments in keyword arguments if possible. + position = 0 + for positional_option in self.__positional_options__: + if positional_option in missing: + kwargs[positional_option] = args[position] + position += 1 + missing.remove(positional_option) + # complain if there are still missing options. if len(missing): raise TypeError( '{}() missing {} required option{}: {}.'.format( @@ -73,6 +62,7 @@ class Configurable(metaclass=ConfigurableMeta): ) ) + # complain if there is more options than possible. extraneous = set(kwargs.keys()) - set(type(self).__options__.keys()) if len(extraneous): raise TypeError( @@ -82,5 +72,6 @@ class Configurable(metaclass=ConfigurableMeta): ) ) + # set option values. for name, value in kwargs.items(): setattr(self, name, value) diff --git a/bonobo/config/options.py b/bonobo/config/options.py index 4a7904b..c0a3fd0 100644 --- a/bonobo/config/options.py +++ b/bonobo/config/options.py @@ -5,10 +5,11 @@ class Option: """ _creation_counter = 0 - def __init__(self, type=None, *, required=False, default=None): + def __init__(self, type=None, *, required=False, positional=False, default=None): self.name = None self.type = type self.required = required + self.positional = positional self.default = default # This hack is necessary for python3.5 @@ -24,4 +25,6 @@ class Option: return inst.__options_values__[self.name] def __set__(self, inst, value): - inst.__options_values__[self.name] = self.type(value) if self.type else value \ No newline at end of file + inst.__options_values__[self.name] = self.type(value) if self.type else value + + diff --git a/bonobo/io/file.py b/bonobo/io/file.py index 49c750b..d0d0af2 100644 --- a/bonobo/io/file.py +++ b/bonobo/io/file.py @@ -20,7 +20,7 @@ class FileHandler(Configurable): fs (str): service name to use for filesystem. """ - path = Option(str, required=True) # type: str + path = Option(str, required=True, positional=True) # type: str eol = Option(str, default='\n') # type: str mode = Option(str) # type: str diff --git a/bonobo/util/objects.py b/bonobo/util/objects.py index 645890b..3b228dd 100644 --- a/bonobo/util/objects.py +++ b/bonobo/util/objects.py @@ -24,6 +24,7 @@ class ValueHolder: For the sake of concistency, all operator methods have been implemented (see https://docs.python.org/3/reference/datamodel.html) or at least all in a certain category, but it feels like a more correct method should exist, like with a getattr-something on the value. Let's see later. + """ def __init__(self, value, *, type=None): diff --git a/tests/fixme_config.py b/tests/test_config.py similarity index 100% rename from tests/fixme_config.py rename to tests/test_config.py