Greem666
Greem666

Reputation: 949

Python got multiple values for argument

I have an object, with a Dependency Injection field for objects implementing method provide():

class DataContainer():

    PROVIDERS = dict()

    def __init__(self):
        self.provider = None

    def provide(self, *args, **kwargs):
        if self.provider:
            return self.provider.provide(self, *args, **kwargs)
        raise KeyError("No provider selected")

I wrote a custom Provider object for it as follows:

# INTERFACE:

class Provider(ABC):
    @abstractmethod
    def provide(self, *args, **kwargs):
        pass

    @abstractmethod
    def add_product(self, name: str, product: ProviderProduct, *args, **kwargs) -> None:
        pass

    @abstractmethod
    def remove_product(self, name: str, *args, **kwargs) -> None:
        pass

class ProviderProduct(ABC):
    @abstractmethod
    def configure(self, *args, **kwargs) -> ProviderProduct:
        pass


IMPLEMENTATION:

class TestProvider(Provider):
    REGISTERED = dict()

    def provide(self, product, from_dict, *args, **kwargs):
        if product in TestProvider.REGISTERED:
            return TestProvider.REGISTERED[product].configure(from_dict)
        raise KeyError("{} not in REGISTERED".format(product))

class AbstractTestProduct(ProviderProduct, ABC):
    INDEX = [0]
    COLUMNS = list()

    def configure(self, from_dict: Dict) -> ProviderProduct:
        df = pd.DataFrame(index=self.INDEX, columns=self.COLUMNS)
        df.update(from_dict)
        return df

    def add_product(self, name, product):
        if isinstance(product, AbstractTestProduct):
            TestProvider.REGISTERED[name] = product
        else:
            raise ValueError("Given {} class is not of AbstractTestProduct type".format(product.__class__.__name__))

    def remove_product(self, name):
        if name in TestProvider.REGISTERED.keys():
            del TestProvider.REGISTERED[name]
        else:
            raise KeyError("{} not found in registered list".format(name))

Now, when I wrote some unit tests for it, things go wrong:

class TestProviderTestSuite(unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        class TestProviderProduct1(AbstractTestProduct):
            COLUMNS = ['test_col_1_1', 'test_col_1_2', 'test_col_1_3']

        class TestProviderProduct2(AbstractTestProduct):
            COLUMNS = ['test_col_2_1', 'test_col_2_2', 'test_col_2_3', 'test_col_2_4']

        class TestProviderProduct3(AbstractTestProduct):
            COLUMNS = ['test_col_3_1', 'test_col_3_2', 'test_col_3_3', 'test_col_3_4', 'test_col_3_5']
        
        cls.data_container = DataContainer()
        cls.data_container.register_provider("dataframe", TestProvider())
        cls.data_container.change_provider('dataframe')
        cls.data_container.add_provider_product("a", TestProviderProduct1())
        cls.data_container.add_provider_product("b", TestProviderProduct2())
        cls.data_container.add_provider_product("c", TestProviderProduct3())

    def test_should_provide_empty_product_df_a(self):
        # Given
        # -
        
        # When
        product_df_a = self.data_container.provide(product="a")

        # Then
        self.assertEqual(3, len(product_df_a.columns))
        self.assertEqual(1, len(product_df_a.index))
        self.assertTrue(0 in product_df_a.index)
        self.assertTrue(product_df_a.isnull().all(axis='columns').values[0])

I get the following error for the test above:

self = <utils.providers.test_provider.TestProviderTestSuite testMethod=test_should_provide_empty_product_df_a>

    def test_should_provide_empty_product_df_a(self):
        # Given
        # -
    
        # When
>       product_df_a = self.data_container.provide(product="a")

tests\test_provider.py:43: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <DataContainer.DataContainer object at 0x0000022FF859CB48>, args = (), kwargs = {'product': 'a'}

    def provide(self, *args, **kwargs):
        if self.provider:
>           return self.provider.provide(self, *args, **kwargs)
E           TypeError: provide() got multiple values for argument 'product'

I don`t understand why interpreter, when it receives a call to DataContainer's method .provide(product="a") determines that 'product' keyworded argument is passed twice.

Any ideas?

This is happening on Python 3.7 for me.

Upvotes: 0

Views: 8201

Answers (1)

Sylvaus
Sylvaus

Reputation: 884

In DataContainer, the function provide takes for arguments *args and **kwargs which means that when you call it in the test, product="a" will be collected in the kwargs.

This implies that when you use the provide function of the TestProvider, you will not get product as a first argument but as a keyword argument (in **kwargs). The second product comes from the positional argument which is getting filled by the self that you pass as parameter (Are you sure you want to pass self as the first parameter?).

They are many way to fix this issue, the simplest would be to make the signature of all the provide identical

Upvotes: 2

Related Questions