ets-labs / python-dependency-injector

Dependency injection framework for Python
https://python-dependency-injector.ets-labs.org/
BSD 3-Clause "New" or "Revised" License
3.69k stars 295 forks source link

Async SQLAlchemy: TypeError: cannot pickle 'module' object #757

Closed Maksim-Burtsev closed 7 months ago

Maksim-Burtsev commented 8 months ago

I use https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/fastapi-sqlalchemy as example for use DI in project with fastAPI + sqlalchemy (async), but when i try to start webapp i have this error:

  line 22, in <module>
    container = Container()
                ^^^^^^^^^^^
  File "src/dependency_injector/containers.pyx", line 730, in dependency_injector.containers.DeclarativeContainer.__new__
  File "src/dependency_injector/providers.pyx", line 4913, in dependency_injector.providers.deepcopy
  File "src/dependency_injector/providers.pyx", line 4920, in dependency_injector.providers.deepcopy
  File "/opt/homebrew/Cellar/python@3.11/3.11.5/Frameworks/Python.framework/Versions/3.11/lib/python3.11/copy.py", line 145, in deepcopy
    y = copier(x, memo)
        ^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/python@3.11/3.11.5/Frameworks/Python.framework/Versions/3.11/lib/python3.11/copy.py", line 230, in _deepcopy_dict
    y[deepcopy(key, memo)] = deepcopy(value, memo)
                             ^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/python@3.11/3.11.5/Frameworks/Python.framework/Versions/3.11/lib/python3.11/copy.py", line 152, in deepcopy
    y = copier(memo)
        ^^^^^^^^^^^^
  File "src/dependency_injector/providers.pyx", line 2536, in dependency_injector.providers.Factory.__deepcopy__
  File "src/dependency_injector/providers.pyx", line 4920, in dependency_injector.providers.deepcopy
  File "/opt/homebrew/Cellar/python@3.11/3.11.5/Frameworks/Python.framework/Versions/3.11/lib/python3.11/copy.py", line 145, in deepcopy
    y = copier(x, memo)
        ^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/python@3.11/3.11.5/Frameworks/Python.framework/Versions/3.11/lib/python3.11/copy.py", line 230, in _deepcopy_dict
    y[deepcopy(key, memo)] = deepcopy(value, memo)
                             ^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/python@3.11/3.11.5/Frameworks/Python.framework/Versions/3.11/lib/python3.11/copy.py", line 160, in deepcopy
    rv = reductor(4)
         ^^^^^^^^^^^
TypeError: cannot pickle 'module' object

I have some differences instead of example:

my database.py

import logging

from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine

from sqlalchemy.orm import declarative_base
from settings import settings

logger = logging.getLogger(__name__)

Base = declarative_base()

class Database:
    def __init__(self) -> None:
        self.engine = create_async_engine(settings.get_pg_url(), pool_size=100,)
        self.session_maker = async_sessionmaker(self.engine, expire_on_commit=False,)

my container.py:

"""Containers module."""

from dependency_injector import containers, providers

from database import Database
from services import *
from infrastructure.repositories import *

class Container(containers.DeclarativeContainer):
    db = providers.Singleton(Database)

    # repositories
    foo_repo = providers.Factory(
        FooRepository,
        session_factory=db.provided.session_maker,
    )
    ...

    #services
    foo_service = providers.Factory(
        FooService,
        repo=foo_repo,
    )
    ...

and with app creation i have


container = Container()
db = container.db()
app.container: Container = container
app.container.wire(
    [
        "src.routers",
        "src.services",
    ]
)

What this error means and how can i fix it?

(python 3.11)

Maksim-Burtsev commented 7 months ago

I found the way to make it work

I hope this will help those who face the same error:

container.py

from dependency_injector import containers, providers

from dependency_injector import containers, providers
from sqlalchemy import orm
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine

from infrastructure.repositories import *
from services import *

class Container(containers.DeclarativeContainer):
    # db
    engine = providers.Singleton(
        create_async_engine,
        DATABASE_URL,
        pool_size=100,
    )
    async_session = providers.Factory(
        orm.sessionmaker,
        bind=engine,
        class_=AsyncSession,
        expire_on_commit=False,
    )
    # repositories
    foo_repo = providers.Factory(
        FooRepository,
        session=async_session,
    )
    ...

    #services
    foo_service = providers.Singleton(
        FooService,
        repo=foo_repo,
    )
    ...

with app creation:


app.container: Container = Container()
app.container.wire(
    [
        "routers.foo_router",
         ...
    ]

and now everything works in routers/foo_router.py:

@router.get(
    "/",
    response_model=list[FooSchema],
)
@inject
async def get_foos(
    foo_service: FooService = Depends(Provide[Container.foo_service]),
) -> list[FooModel]:
    return await foo_service.get_objects()