ets-labs/python-dependency-injector

Cannot use class variable to create instance in DI container

Closed this issue · 1 comments

Python 3.12 and dependency-injector 4.45.0
Here is my code structure:

class MyModel(BaseModel):
	...
# full class definition of EntityMapper
class EntityMapper:
	def __init__(
        self,
        dict_to_model: Callable[[dict], DomainModel],
        model_to_dict: Callable[[DomainModel], dict],
        filter_to_query: Callable[[dict], dict],
    ):
        self.dict_to_model = dict_to_model
        self.model_to_dict = model_to_dict
        self.filter_to_query = filter_to_query
class GenericDAO:
	def __init__(self, model_type: type, collection_name: str, entity_mapper: EntityMapper):
		...
	...
class MyEntityMapperUtil:
	@staticmethod
	def model_to_dict(model: MyModel) -> dict:
		...
		
	@staticmethod
	def dict_to_model(dict_from_mongodb: dict) -> MyModel:
		...
		
	@staticmethod
	def filter_to_query(_filter: dict) -> dict:
		...

	entity_mapper = EntityMapper(
            dict_to_model=dict_to_model,
            model_to_dict=model_to_dict,
            filter_to_query=filter_to_query,
	)
	
my_entity_mapper_x1 = EntityMapper(
        dict_to_model=MyEntityMapperUtil.dict_to_model,
        model_to_dict=MyEntityMapperUtil.model_to_dict,
        filter_to_query=MyEntityMapperUtil.filter_to_query,
	)

my_entity_mapper_x2 = MyEntityMapperUtil.entity_mapper
class MyDIContainer(containers.DeclarativeContainer):
      wiring_config = containers.WiringConfiguration(packages=["<package-name>")

    **# This is OK**
    my_dao = providers.Singleton(
          GenericDAO,
          MyModel,
          "<my_collection>",
          my_entity_mapper_x1,			<--
    )

    **# This is OK**
    my_dao = providers.Singleton(
        GenericDAO,
        MyModel,
        "<my_collection>",
        EntityMapper(					<--
            dict_to_model=MyEntityMapperUtil.dict_to_model,
            model_to_dict=MyEntityMapperUtil.model_to_dict,
            filter_to_query=MyEntityMapperUtil.filter_to_query,
        ),
    )

    **# This fails with NonCopyableArgumentError**
    my_dao = providers.Singleton(
        GenericDAO,
        MyModel,
        "<my_collection>",
        MyEntityMapperUtil.entity_mapper,	<--
    )

    **# This fails with NonCopyableArgumentError**
    my_dao = providers.Singleton(
        GenericDAO,
        MyModel,
        "<my_collection>",
        my_entity_mapper_x2,			<--
    )

Provider arguments must be deepcopy-able. Given your particular example, if you check the stack trace, there should be an error TypeError: cannot pickle 'staticmethod' object. Which means, in MyEntityMapperUtil.entity_mapper you're referencing wrappers, not functions themselves (at this point, transformation to real static method not yet happened). And staticmethod instances are not deepcopy-able.

If you really want to keep your code structure, you can do something like this instead:

     def filter_to_query(_filter: dict) -> dict:
         ...
 
-    entity_mapper = EntityMapper(
-        dict_to_model=dict_to_model,
-        model_to_dict=model_to_dict,
-        filter_to_query=filter_to_query,
-    )
+MyEntityMapperUtil.entity_mapper = EntityMapper(
+    dict_to_model=MyEntityMapperUtil.dict_to_model,
+    model_to_dict=MyEntityMapperUtil.model_to_dict,
+    filter_to_query=MyEntityMapperUtil.filter_to_query,
+)
 
 
 my_entity_mapper_x1 = EntityMapper(

But on a personal note, I would recommend against any generic DAO/Repository implementations. It is difficult to get it right.