Reputation: 949
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
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