Issue with indexes already existing or duplicating
I am getting an error trying to apply the [sqlalchemy example ](https://github.com/dbfixtures/pytest-postgresql?tab=readme-ov-file#using-sqlalchemy-to-initialise-basic-database-state). Specifically it seems that it's trying to create an index again. The relevant fixtures look like this:
```python
import psycopg
def load_database(**kwargs):
"""
Initialize the database template for unit tests.
"""
connection = psycopg.connect(**kwargs)
with connection.cursor() as cur:
cur.execute("CREATE EXTENSION IF NOT EXISTS pg_trgm;")
connection.commit()
connection.close()
engine = create_engine(f"postgresql+psycopg2://{kwargs['user']}:@{kwargs['host']}:{kwargs['port']}/{kwargs['dbname']}")
Base.metadata.create_all(engine)
session = scoped_session(sessionmaker(bind=engine))
# NOTE add anything else shared by all tests to session here
session.commit()
postgresql_proc = factories.postgresql_proc(load=[load_database])
# postgresql is a fixture for postgresql_proc
# it has function scope
postgresql = factories.postgresql('postgresql_proc')
@pytest.fixture
def db_session(postgresql) -> Generator[OrmSession, None, None]:
"""
Function-scoped database transaction.
All data in the transaction is rolled back between test classes.
"""
connection = f'postgresql+psycopg2://{postgresql.info.user}:@{postgresql.info.host}:{postgresql.info.port}/{postgresql.info.dbname}'
engine = create_engine(connection)
session = scoped_session(
sessionmaker(autocommit=False, autoflush=False, bind=engine)
)
yield session
session.close()
```
My understanding is that load_database will be used to create a template all tests will start from.
```
______________ ERROR at setup of test_my_test _______________
self = <sqlalchemy.engine.base.Connection object at 0xffff8bfe1540>
dialect = <sqlalchemy.dialects.postgresql.psycopg2.PGDialect_psycopg2 object at 0xffff8bfe1780>
context = <sqlalchemy.dialects.postgresql.psycopg2.PGExecutionContext_psycopg2 object at 0xffff8bf752d0>
statement = <sqlalchemy.dialects.postgresql.base.PGDDLCompiler object at 0xffff8bfe0c40>
parameters = [immutabledict({})]
def _exec_single_context(
self,
dialect: Dialect,
context: ExecutionContext,
statement: Union[str, Compiled],
parameters: Optional[_AnyMultiExecuteParams],
) -> CursorResult[Any]:
"""continue the _execute_context() method for a single DBAPI
cursor.execute() or cursor.executemany() call.
"""
if dialect.bind_typing is BindTyping.SETINPUTSIZES:
generic_setinputsizes = context._prepare_set_input_sizes()
if generic_setinputsizes:
try:
dialect.do_set_input_sizes(
context.cursor, generic_setinputsizes, context
)
except BaseException as e:
self._handle_dbapi_exception(
e, str(statement), parameters, None, context
)
cursor, str_statement, parameters = (
context.cursor,
context.statement,
context.parameters,
)
effective_parameters: Optional[_AnyExecuteParams]
if not context.executemany:
effective_parameters = parameters[0]
else:
effective_parameters = parameters
if self._has_events or self.engine._has_events:
for fn in self.dispatch.before_cursor_execute:
str_statement, effective_parameters = fn(
self,
cursor,
str_statement,
effective_parameters,
context,
context.executemany,
)
if self._echo:
self._log_info(str_statement)
stats = context._get_cache_stats()
if not self.engine.hide_parameters:
self._log_info(
"[%s] %r",
stats,
sql_util._repr_params(
effective_parameters,
batches=10,
ismulti=context.executemany,
),
)
else:
self._log_info(
"[%s] [SQL parameters hidden due to hide_parameters=True]",
stats,
)
evt_handled: bool = False
try:
if context.execute_style is ExecuteStyle.EXECUTEMANY:
effective_parameters = cast(
"_CoreMultiExecuteParams", effective_parameters
)
if self.dialect._has_events:
for fn in self.dialect.dispatch.do_executemany:
if fn(
cursor,
str_statement,
effective_parameters,
context,
):
evt_handled = True
break
if not evt_handled:
self.dialect.do_executemany(
cursor,
str_statement,
effective_parameters,
context,
)
elif not effective_parameters and context.no_parameters:
if self.dialect._has_events:
for fn in self.dialect.dispatch.do_execute_no_params:
if fn(cursor, str_statement, context):
evt_handled = True
break
if not evt_handled:
self.dialect.do_execute_no_params(
cursor, str_statement, context
)
else:
effective_parameters = cast(
"_CoreSingleExecuteParams", effective_parameters
)
if self.dialect._has_events:
for fn in self.dialect.dispatch.do_execute:
if fn(
cursor,
str_statement,
effective_parameters,
context,
):
evt_handled = True
break
if not evt_handled:
> self.dialect.do_execute(
cursor, str_statement, effective_parameters, context
)
/usr/local/lib/python3.10/site-packages/sqlalchemy/engine/base.py:1967:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <sqlalchemy.dialects.postgresql.psycopg2.PGDialect_psycopg2 object at 0xffff8bfe1780>
cursor = <cursor object at 0xffff8b9f86d0; closed: -1>
statement = 'CREATE INDEX ix_user_distribution_settings_category ON user_distribution_settings (category)'
parameters = immutabledict({})
context = <sqlalchemy.dialects.postgresql.psycopg2.PGExecutionContext_psycopg2 object at 0xffff8bf752d0>
def do_execute(self, cursor, statement, parameters, context=None):
> cursor.execute(statement, parameters)
E psycopg2.errors.DuplicateTable: relation "ix_user_distribution_settings_category" already exists
/usr/local/lib/python3.10/site-packages/sqlalchemy/engine/default.py:941: DuplicateTable
The above exception was the direct cause of the following exception:
fixturedef = <FixtureDef argname='postgresql' scope='function' baseid='tests'>
request = <SubRequest 'postgresql' for <Function test__check_user_contact_groups>>
@pytest.hookimpl(wrapper=True)
def pytest_fixture_setup(fixturedef: FixtureDef, request) -> object | None:
asyncio_mode = _get_asyncio_mode(request.config)
if not _is_asyncio_fixture_function(fixturedef.func):
if asyncio_mode == Mode.STRICT:
# Ignore async fixtures without explicit asyncio mark in strict mode
# This applies to pytest_trio fixtures, for example
return (yield)
if not _is_coroutine_or_asyncgen(fixturedef.func):
> return (yield)
/usr/local/lib/python3.10/site-packages/pytest_asyncio/plugin.py:735:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/usr/local/lib/python3.10/site-packages/pytest_postgresql/factories/client.py:53: in postgresql_factory
proc_fixture: Union[PostgreSQLExecutor, NoopExecutor] = request.getfixturevalue(
/usr/local/lib/python3.10/site-packages/pytest_asyncio/plugin.py:735: in pytest_fixture_setup
return (yield)
/usr/local/lib/python3.10/site-packages/pytest_postgresql/factories/process.py:184: in postgresql_proc_fixture
janitor.load(load_element)
/usr/local/lib/python3.10/site-packages/pytest_postgresql/janitor.py:121: in load
_loader(
tests/conftest.py:305: in load_database
Base.metadata.create_all(engine)
/usr/local/lib/python3.10/site-packages/sqlalchemy/sql/schema.py:5868: in create_all
bind._run_ddl_visitor(
/usr/local/lib/python3.10/site-packages/sqlalchemy/engine/base.py:3253: in _run_ddl_visitor
conn._run_ddl_visitor(visitorcallable, element, **kwargs)
/usr/local/lib/python3.10/site-packages/sqlalchemy/engine/base.py:2459: in _run_ddl_visitor
visitorcallable(self.dialect, self, **kwargs).traverse_single(element)
/usr/local/lib/python3.10/site-packages/sqlalchemy/sql/visitors.py:664: in traverse_single
return meth(obj, **kw)
/usr/local/lib/python3.10/site-packages/sqlalchemy/sql/ddl.py:918: in visit_metadata
self.traverse_single(
/usr/local/lib/python3.10/site-packages/sqlalchemy/sql/visitors.py:664: in traverse_single
return meth(obj, **kw)
/usr/local/lib/python3.10/site-packages/sqlalchemy/sql/ddl.py:960: in visit_table
self.traverse_single(index, create_ok=True)
/usr/local/lib/python3.10/site-packages/sqlalchemy/sql/visitors.py:664: in traverse_single
return meth(obj, **kw)
/usr/local/lib/python3.10/site-packages/sqlalchemy/sql/ddl.py:997: in visit_index
CreateIndex(index)._invoke_with(self.connection)
/usr/local/lib/python3.10/site-packages/sqlalchemy/sql/ddl.py:314: in _invoke_with
return bind.execute(self)
/usr/local/lib/python3.10/site-packages/sqlalchemy/engine/base.py:1418: in execute
return meth(
/usr/local/lib/python3.10/site-packages/sqlalchemy/sql/ddl.py:180: in _execute_on_connection
return connection._execute_ddl(
/usr/local/lib/python3.10/site-packages/sqlalchemy/engine/base.py:1529: in _execute_ddl
ret = self._execute_context(
/usr/local/lib/python3.10/site-packages/sqlalchemy/engine/base.py:1846: in _execute_context
return self._exec_single_context(
/usr/local/lib/python3.10/site-packages/sqlalchemy/engine/base.py:1986: in _exec_single_context
self._handle_dbapi_exception(
/usr/local/lib/python3.10/site-packages/sqlalchemy/engine/base.py:2355: in _handle_dbapi_exception
raise sqlalchemy_exception.with_traceback(exc_info[2]) from e
/usr/local/lib/python3.10/site-packages/sqlalchemy/engine/base.py:1967: in _exec_single_context
self.dialect.do_execute(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <sqlalchemy.dialects.postgresql.psycopg2.PGDialect_psycopg2 object at 0xffff8bfe1780>
cursor = <cursor object at 0xffff8b9f86d0; closed: -1>
statement = 'CREATE INDEX ix_user_distribution_settings_category ON user_distribution_settings (category)'
parameters = immutabledict({})
context = <sqlalchemy.dialects.postgresql.psycopg2.PGExecutionContext_psycopg2 object at 0xffff8bf752d0>
def do_execute(self, cursor, statement, parameters, context=None):
> cursor.execute(statement, parameters)
E sqlalchemy.exc.ProgrammingError: (psycopg2.errors.DuplicateTable) relation "ix_user_distribution_settings_category" already exists
E
E [SQL: CREATE INDEX ix_user_distribution_settings_category ON user_distribution_settings (category)]
E (Background on this error at: https://sqlalche.me/e/20/f405)
/usr/local/lib/python3.10/site-packages/sqlalchemy/engine/default.py:941: ProgrammingError
```
关闭于 2025-12-17 9 条评论