【问题标题】:SQL Alchemy & Docker Compose: Maximum recursion errorSQL Alchemy & Docker Compose:最大递归错误
【发布时间】:2021-01-04 16:24:16
【问题描述】:

我正在尝试使用 SQL Alchemy 和 docker-compose 将用户添加到 PSQL DB。我有 2 个容器:“db”和“py”。构建并运行容器后,我点击“py”容器中的 API 来存储用户。当我运行db_session.commit() 来保存用户时,我收到以下错误:

RecursionError: 超出最大递归深度

当我在本地运行此代码时,它可以完美运行。我将数据库主机更改为localhost,并且用户已正确存储在数据库中。在 docker 容器中运行时,出现递归错误。

db/handler.py

    @contextmanager
    def session_scope():
    engine = create_engine('postgresql+psycopg2://postgres:postgres@db:5432/my_db', echo=True)
    session_factory = sessionmaker(bind=engine)
    db_session = scoped_session(session_factory)
    try:
        yield db_session
    except:
        db_session.rollback()
    finally:
        db_session.close()

docker-compose.yaml

services:
    py01:
        build: ./py
        image: py
        depends_on:
            - db
        container_name: py
        expose:
            - 8000

    db01:
        build: ./db
        image: db
        container_name: db
        ports:
            - "5432:5432"

users.py

with session_scope() as db_session:
    user = User(user_class="tempweb", temp_web_id=temp_web_id)

    db_session.add(user)

    db_session.commit() # <---- MAXIMUM RECURSION DEPTH EXCEEDED error occurs here

堆栈回溯

py         | ERROR:    Exception in ASGI application
py         | Traceback (most recent call last):
py         |   File "/opt/pypy/site-packages/uvicorn/protocols/http/h11_impl.py", line 388, in run_asgi
py         |     result = await app(self.scope, self.receive, self.send)
py         |   File "/opt/pypy/site-packages/uvicorn/middleware/proxy_headers.py", line 45, in __call__
py         |     return await self.app(scope, receive, send)
py         |   File "/opt/pypy/site-packages/fastapi/applications.py", line 182, in __call__
py         |     await super().__call__(scope, receive, send)  # pragma: no cover
py         |   File "/opt/pypy/site-packages/starlette/applications.py", line 111, in __call__
py         |     await self.middleware_stack(scope, receive, send)
py         |   File "/opt/pypy/site-packages/starlette/middleware/errors.py", line 181, in __call__
py         |     raise exc from None
py         |   File "/opt/pypy/site-packages/starlette/middleware/errors.py", line 159, in __call__
py         |     await self.app(scope, receive, _send)
py         |   File "/opt/pypy/site-packages/starlette/middleware/cors.py", line 78, in __call__
py         |     await self.app(scope, receive, send)
py         |   File "/opt/pypy/site-packages/starlette/exceptions.py", line 82, in __call__
py         |     raise exc from None
py         |   File "/opt/pypy/site-packages/starlette/exceptions.py", line 71, in __call__
py         |     await self.app(scope, receive, sender)
py         |   File "/opt/pypy/site-packages/starlette/routing.py", line 566, in __call__
py         |     await route.handle(scope, receive, send)
py         |   File "/opt/pypy/site-packages/starlette/routing.py", line 227, in handle
py         |     await self.app(scope, receive, send)
py         |   File "/opt/pypy/site-packages/starlette/routing.py", line 41, in app
py         |     response = await func(request)
py         |   File "/opt/pypy/site-packages/fastapi/routing.py", line 197, in app
py         |     dependant=dependant, values=values, is_coroutine=is_coroutine
py         |   File "/opt/pypy/site-packages/fastapi/routing.py", line 149, in run_endpoint_function
py         |     return await run_in_threadpool(dependant.call, **values)
py         |   File "/opt/pypy/site-packages/starlette/concurrency.py", line 34, in run_in_threadpool
py         |     return await loop.run_in_executor(None, func, *args)
py         |   File "/opt/pypy/lib-python/3/asyncio/futures.py", line 327, in __iter__
py         |     yield self  # This tells Task to wait for completion.
py         |   File "/opt/pypy/lib-python/3/asyncio/tasks.py", line 250, in _wakeup
py         |     future.result()
py         |   File "/opt/pypy/lib-python/3/asyncio/futures.py", line 243, in result
py         |     raise self._exception
py         |   File "/opt/pypy/lib-python/3/concurrent/futures/thread.py", line 56, in run
py         |     result = self.fn(*self.args, **self.kwargs)
py         |   File "./app/routers/users.py", line 33, in get_session
py         |     with session_scope() as db_session:
py         |   File "/opt/pypy/lib-python/3/contextlib.py", line 81, in __enter__
py         |     return next(self.gen)
py         |   File "./app/db/handler.py", line 10, in session_scope
py         |     engine.connect()
py         |   File "/opt/pypy/site-packages/sqlalchemy/engine/base.py", line 2265, in connect
py         |     return self._connection_cls(self, **kwargs)
py         |   File "/opt/pypy/site-packages/sqlalchemy/engine/base.py", line 104, in __init__
py         |     else engine.raw_connection()
py         |   File "/opt/pypy/site-packages/sqlalchemy/engine/base.py", line 2372, in raw_connection
py         |     self.pool.unique_connection, _connection
py         |   File "/opt/pypy/site-packages/sqlalchemy/engine/base.py", line 2338, in _wrap_pool_connect
py         |     return fn()
py         |   File "/opt/pypy/site-packages/sqlalchemy/pool/base.py", line 304, in unique_connection
py         |     return _ConnectionFairy._checkout(self)
py         |   File "/opt/pypy/site-packages/sqlalchemy/pool/base.py", line 778, in _checkout
py         |     fairy = _ConnectionRecord.checkout(pool)
py         |   File "/opt/pypy/site-packages/sqlalchemy/pool/base.py", line 495, in checkout
py         |     rec = pool._do_get()
py         |   File "/opt/pypy/site-packages/sqlalchemy/pool/impl.py", line 140, in _do_get
py         |     self._dec_overflow()
py         |   File "/opt/pypy/site-packages/sqlalchemy/util/langhelpers.py", line 69, in __exit__
py         |     exc_value, with_traceback=exc_tb,
py         |   File "/opt/pypy/site-packages/sqlalchemy/util/compat.py", line 182, in raise_
py         |     raise exception
py         |   File "/opt/pypy/site-packages/sqlalchemy/pool/impl.py", line 137, in _do_get
py         |     return self._create_connection()
py         |   File "/opt/pypy/site-packages/sqlalchemy/pool/base.py", line 309, in _create_connection
py         |     return _ConnectionRecord(self)
py         |   File "/opt/pypy/site-packages/sqlalchemy/pool/base.py", line 440, in __init__
py         |     self.__connect(first_connect_check=True)
py         |   File "/opt/pypy/site-packages/sqlalchemy/pool/base.py", line 666, in __connect
py         |     ).exec_once_unless_exception(self.connection, self)
py         |   File "/opt/pypy/site-packages/sqlalchemy/event/attr.py", line 314, in exec_once_unless_exception
py         |     self._exec_once_impl(True, *args, **kw)
py         |   File "/opt/pypy/site-packages/sqlalchemy/event/attr.py", line 285, in _exec_once_impl
py         |     self(*args, **kw)
py         |   File "/opt/pypy/site-packages/sqlalchemy/event/attr.py", line 322, in __call__
py         |     fn(*args, **kw)
py         |   File "/opt/pypy/site-packages/sqlalchemy/util/langhelpers.py", line 1513, in go
py         |     return once_fn(*arg, **kw)
py         |   File "/opt/pypy/site-packages/sqlalchemy/engine/strategies.py", line 199, in first_connect
py         |     dialect.initialize(c)
py         |   File "/opt/pypy/site-packages/sqlalchemy/dialects/postgresql/psycopg2.py", line 732, in initialize
py         |     super(PGDialect_psycopg2, self).initialize(connection)
py         |   File "/opt/pypy/site-packages/sqlalchemy/dialects/postgresql/base.py", line 2562, in initialize
py         |     super(PGDialect, self).initialize(connection)
py         |   File "/opt/pypy/site-packages/sqlalchemy/engine/default.py", line 313, in initialize
py         |     connection
py         |   File "/opt/pypy/site-packages/sqlalchemy/dialects/postgresql/base.py", line 2803, in _get_server_version_info
py         |     v = connection.execute("select version()").scalar()
py         |   File "/opt/pypy/site-packages/sqlalchemy/engine/base.py", line 1003, in execute
py         |     return self._execute_text(object_, multiparams, params)
py         |   File "/opt/pypy/site-packages/sqlalchemy/engine/base.py", line 1178, in _execute_text
py         |     parameters,
py         |   File "/opt/pypy/site-packages/sqlalchemy/engine/base.py", line 1317, in _execute_context
py         |     e, statement, parameters, cursor, context
py         |   File "/opt/pypy/site-packages/sqlalchemy/engine/base.py", line 1514, in _handle_dbapi_exception
py         |     util.raise_(exc_info[1], with_traceback=exc_info[2])
py         |   File "/opt/pypy/site-packages/sqlalchemy/util/compat.py", line 182, in raise_
py         |     raise exception
py         |   File "/opt/pypy/site-packages/sqlalchemy/engine/base.py", line 1294, in _execute_context
py         |     result = context._setup_crud_result_proxy()
py         |   File "/opt/pypy/site-packages/sqlalchemy/engine/default.py", line 1243, in _setup_crud_result_proxy
py         |     result = self.get_result_proxy()
py         |   File "/opt/pypy/site-packages/sqlalchemy/dialects/postgresql/psycopg2.py", line 601, in get_result_proxy
py         |     return _result.ResultProxy(self)
py         |   File "/opt/pypy/site-packages/sqlalchemy/engine/result.py", line 775, in __init__
py         |     self._init_metadata()
py         |   File "/opt/pypy/site-packages/sqlalchemy/engine/result.py", line 807, in _init_metadata
py         |     self._metadata = ResultMetaData(self, cursor_description)
py         |   File "/opt/pypy/site-packages/sqlalchemy/engine/result.py", line 296, in __init__
py         |     textual_ordered,
py         |   File "/opt/pypy/site-packages/sqlalchemy/engine/result.py", line 514, in _merge_cursor_description
py         |     ) in raw_iterator
py         |   File "/opt/pypy/site-packages/sqlalchemy/engine/result.py", line 497, in <listcomp>
py         |     (
py         |   File "/opt/pypy/site-packages/sqlalchemy/engine/result.py", line 621, in _merge_cols_by_none
py         |     ) in self._colnames_from_description(context, cursor_description):
py         |   File "/opt/pypy/site-packages/sqlalchemy/engine/result.py", line 541, in _colnames_from_description
py         |     colname = rec[0]
py         | RecursionError: maximum recursion depth exceeded

【问题讨论】:

  • 您检查过任何循环依赖问题吗?
  • @onlinejudge95 - 代码在本地运行时完美运行。是否存在仅在 docker 容器中运行时才会发生的循环依赖?
  • 您的数据库架构中可能有循环引用。另外,我没有看到您在 Dockerfile 中运行迁移,您是否处理过这个问题?

标签: python postgresql docker-compose sqlalchemy


【解决方案1】:

我发现了这个问题。这是由于在 DB 连接字符串中使用了psycopg2 驱动程序引起的。当我进入 python 解释器并尝试导入 psycopg2 时,出现导入错误。安装在 Docker 容器上的 python 存在问题(我使用的是 pypy:3),导致该错误发生在容器中,而不是本地发生。

ImportError: No module named 'psycopg2._psycopg'

为了解决这个问题,我只是在数据库连接字符串中选择了一种不同的方言:pg8000,它现在可以在容器中完美运行。

Thanks to this SO post 帮助诊断问题。

【讨论】:

  • 另外,为了好奇,您为什么在 conn 字符串的驱动程序部分使用 postgresql+psycopg2。我假设您已经从源代码构建了 psycopg?
  • SQL Alchemy 文档在教程中使用 postgresql+psycopg2 作为数据库连接 URI 的一部分。我将它包含在 requirements.txt 中以添加到项目中。就像我说的,它在本地运行良好,但我认为 docker 容器中的 python 构建缺少一些库 psycopg2 需要运行,因此模块导入错误。
猜你喜欢
  • 1970-01-01
  • 2016-12-10
  • 1970-01-01
  • 2018-12-03
  • 1970-01-01
  • 1970-01-01
  • 2019-07-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多