From 25253cf11962d5e29bbfa843c56e77017e768052 Mon Sep 17 00:00:00 2001 From: Romain Dorgueil Date: Mon, 1 May 2017 15:26:57 +0200 Subject: [PATCH] Positional options (WIP). --- Projectfile | 2 +- bonobo/config/configurables.py | 36 +++++++++++++++++++++++++++++++--- bonobo/io/csv.py | 2 +- bonobo/io/json.py | 2 +- tests/test_config.py | 10 ++++++++++ 5 files changed, 46 insertions(+), 6 deletions(-) diff --git a/Projectfile b/Projectfile index 73a534c..47844ee 100644 --- a/Projectfile +++ b/Projectfile @@ -1,7 +1,7 @@ # bonobo (see github.com/python-edgy/project) name = 'bonobo' -description = 'Bonobo' +description = 'Bonobo, a simple, modern and atomic extract-transform-load toolkit for python 3.5+.' license = 'Apache License, Version 2.0' url = 'https://www.bonobo-project.org/' diff --git a/bonobo/config/configurables.py b/bonobo/config/configurables.py index d5bdeb3..18b5a8c 100644 --- a/bonobo/config/configurables.py +++ b/bonobo/config/configurables.py @@ -1,5 +1,32 @@ from bonobo.config.options import Option +__all__ = [ + 'Configurable', + '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): """ @@ -25,15 +52,18 @@ class Configurable(metaclass=ConfigurableMeta): """ - def __init__(self, **kwargs): + def __init__(self, *args, **kwargs): super().__init__() self.__options_values__ = {} - missing = set() + missing = list() for name, option in type(self).__options__.items(): if option.required and not option.name in kwargs: - missing.add(name) + missing.append(name) + + for i in range(min(len(args), len(missing))): + kwargs if len(missing): raise TypeError( diff --git a/bonobo/io/csv.py b/bonobo/io/csv.py index 9192fc3..df84557 100644 --- a/bonobo/io/csv.py +++ b/bonobo/io/csv.py @@ -55,7 +55,7 @@ class CsvReader(CsvHandler, FileReader): for row in reader: if len(row) != field_count: - raise ValueError('Got a line with %d fields, expecting %d.' % (len(row), field_count, )) + raise ValueError('Got a line with %d fields, expecting %d.' % (len(row), field_count,)) yield dict(zip(headers.value, row)) diff --git a/bonobo/io/json.py b/bonobo/io/json.py index 34f4f0f..f5bc8b1 100644 --- a/bonobo/io/json.py +++ b/bonobo/io/json.py @@ -8,7 +8,7 @@ __all__ = [ ] -class JsonHandler: +class JsonHandler(): eol = ',\n' prefix, suffix = '[', ']' diff --git a/tests/test_config.py b/tests/test_config.py index 3c46fbc..e6d3916 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -18,6 +18,11 @@ class MyHarderConfigurable(MyConfigurable): class MyBetterConfigurable(MyConfigurable): required_str = Option(str, required=False, default='kaboom') +class MyConfigurableUsingPositionalOptions(MyConfigurable): + first = Option(str, required=True, positional=True) + second = Option(str, required=True, positional=True) + third = Option(str, required=False, positional=True) + class PrinterInterface(): def print(self, *args): @@ -133,3 +138,8 @@ def test_service_dependency_unavailable(): o = MyServiceDependantConfigurable(printer='printer2') with pytest.raises(KeyError): SERVICES.args_for(o) + + +def test_option_positional(): + o = MyConfigurableUsingPositionalOptions('1', '2', '3', required_str='hello') +