Describe the bug
I'm trying to use gino in postgres with simple model with enum column type. It seems like enum is not fully supported and it behaves really strange in several cases:
- when using gino Enum type in
where
filters, asyncpg.exceptions.InternalServerError: cache lookup failed for type 16453
error occurs
- when using postgresql Enum and running
db.gino.create_all()
, asyncpg.exceptions.UndefinedObjectError: type "test-schema.enum1" does not exist
error occurs
To Reproduce
The full example is in my repo: https://github.com/zerlok/python-bugs/tree/gino/enum-usage
import asyncio
import enum
import os
import typing as t
import gino
import pytest
import sqlalchemy as sa
from sqlalchemy.dialects.postgresql import ENUM as PostgresEnum
DB_SCHEMA = os.getenv("SCHEMA")
DB = gino.Gino(schema=DB_SCHEMA)
class Enum1(enum.Enum):
VAL1 = enum.auto()
VAL2 = enum.auto()
class Enum2(enum.Enum):
VAL3 = enum.auto()
VAL4 = enum.auto()
VAL5 = enum.auto()
@pytest.mark.asyncio
class TestGinoEnum:
@pytest.fixture(scope="session")
async def engine(self):
async with DB.with_bind(os.getenv("DATABASE")) as engine:
yield engine
@pytest.fixture(scope="session")
def db_model(self, gino_enum_type, engine) -> gino.Gino.Model:
if gino_enum_type == "postgres":
class Foo(DB.Model):
__tablename__ = "foo"
id = DB.Column(DB.Integer(), primary_key=True)
# causes asyncpg.exceptions.UndefinedObjectError: type "test.enum1" does not exist
enum_field1 = DB.Column(PostgresEnum(Enum1, inherit_schema=True), default=Enum1.VAL1, nullable=False)
enum_field2 = DB.Column(PostgresEnum(Enum2, inherit_schema=True), default=Enum2.VAL3, nullable=False)
elif gino_enum_type is None:
class Foo(DB.Model):
__tablename__ = "foo"
id = DB.Column(DB.Integer(), primary_key=True)
# no exception
enum_field1 = DB.Column(DB.Enum(Enum1, inherit_schema=True), default=Enum1.VAL1, nullable=False)
enum_field2 = DB.Column(DB.Enum(Enum2, inherit_schema=True), default=Enum2.VAL3, nullable=False)
else:
raise ValueError("unknown gino type", gino_enum_type)
return Foo
@pytest.fixture()
async def clean_database(self, gino_enum_type, gino_create_enum_type_manually, engine, db_model):
await DB.gino.drop_all()
if DB_SCHEMA:
await engine.status(f"""drop schema if exists \"{DB_SCHEMA}\" cascade""")
await engine.status(f"""create schema if not exists \"{DB_SCHEMA}\"""")
if gino_create_enum_type_manually:
def quote_value(value: t.Union[Enum1, Enum2]) -> str:
return f"'{value.name}'"
async def create_enum(e: t.Union[t.Type[Enum1], t.Type[Enum2]]) -> None:
if DB_SCHEMA:
enum_type = f"\"{DB_SCHEMA}\".{e.__name__.lower()}"
else:
enum_type = f"{e.__name__.lower()}"
await engine.status(f"""
create type {enum_type} as enum ({",".join(quote_value(v) for v in e)})
""")
await create_enum(Enum1)
await create_enum(Enum2)
else:
# hope that enum types will be added automatically with `create_all` .
pass
await DB.gino.create_all()
async def test_enum_types_removal(self, engine, clean_database) -> None:
await DB.gino.drop_all()
assert (await self.__get_enum_info_from_database(engine)) == []
async def test_enum_type_creation(self, engine, clean_database) -> None:
assert (await self.__get_enum_info_from_database(engine)) == sorted(
(DB_SCHEMA or "public", enum_type.__name__.lower(), val.name)
for enum_type in (Enum1, Enum2)
for val in enum_type
)
@pytest.mark.parametrize("n", (0, 1, 2))
async def test_enum_type_filter(self, engine, clean_database, db_model, n) -> None:
# when n == 2, `the asyncpg.exceptions.InternalServerError: cache lookup failed for type 16453` occurs
assert (await asyncio.gather(*(
self.__get_bars(engine, db_model)
for i in range(n)
))) == [[] for i in range(n)]
async def __get_bars(self, engine: gino.GinoEngine, db_model: gino.Gino.Model) -> [object]:
val1 = await engine.all(db_model.query.where(db_model.enum_field1 == Enum1.VAL1))
val4 = await engine.all(
sa.select([db_model.id]).select_from(db_model).where(db_model.enum_field2 == Enum2.VAL4))
return [*val1, *val4]
async def __get_enum_info_from_database(self, engine: gino.GinoEngine) -> t.Sequence[t.Tuple[str, str, str]]:
return await engine.all("""
select n.nspname as enum_schema, t.typname as enum_name, e.enumlabel as enum_value
from pg_type t
join pg_enum e on t.oid = e.enumtypid
join pg_catalog.pg_namespace n ON n.oid = t.typnamespace
order by enum_schema, enum_name, enum_value
""")
Expected result
I want all my test runs to finish without described exceptions. See README: https://github.com/zerlok/python-bugs/tree/gino/enum-usage#troubles-with-gino-sqlalchemy-postgres-enum
Actual result
For 1 case
FAILED tests/test_gino.py::TestGinoEnum::test_enum_type_filter[2] - asyncpg.exceptions.InternalServerError: cache lookup failed for type 16453
For 2 case
ERROR tests/test_gino.py::TestGinoEnum::test_enum_types_removal - asyncpg.exceptions.UndefinedObjectError: type "test-schema.enum1" does not exist
ERROR tests/test_gino.py::TestGinoEnum::test_enum_type_creation - asyncpg.exceptions.UndefinedObjectError: type "test-schema.enum1" does not exist
ERROR tests/test_gino.py::TestGinoEnum::test_enum_type_filter[0] - asyncpg.exceptions.UndefinedObjectError: type "test-schema.enum1" does not exist
ERROR tests/test_gino.py::TestGinoEnum::test_enum_type_filter[1] - asyncpg.exceptions.UndefinedObjectError: type "test-schema.enum1" does not exist
ERROR tests/test_gino.py::TestGinoEnum::test_enum_type_filter[2] - asyncpg.exceptions.UndefinedObjectError: type "test-schema.enum1" does not exist
Environment (please complete the following information):
asyncpg==0.25.0; python_version >= "3.5" and python_version < "4.0" and python_full_version >= "3.6.0"
atomicwrites==1.4.0; python_version >= "3.7" and python_full_version < "3.0.0" and sys_platform == "win32" or sys_platform == "win32" and python_version >= "3.7" and python_full_version >= "3.4.0"
attrs==21.4.0; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.7"
click==8.1.3; python_version >= "3.7"
colorama==0.4.4; python_version >= "3.7" and python_full_version < "3.0.0" and sys_platform == "win32" and platform_system == "Windows" or sys_platform == "win32" and python_version >= "3.7" and python_full_version >= "3.5.0" and platform_system == "Windows"
gino==1.0.1; python_version >= "3.5" and python_version < "4.0"
iniconfig==1.1.1; python_version >= "3.7"
packaging==21.3; python_version >= "3.7"
pluggy==1.0.0; python_version >= "3.7"
py==1.11.0; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.7"
pyparsing==3.0.9; python_full_version >= "3.6.8" and python_version >= "3.7"
pytest-asyncio==0.18.3; python_version >= "3.7"
pytest==7.1.2; python_version >= "3.7"
sqlalchemy==1.3.24; python_version >= "3.5" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.5" and python_version < "4.0" and python_full_version >= "3.4.0"
tomli==2.0.1; python_version >= "3.7"
wait-for-it==2.2.1; python_version >= "3.7"
postgres: 11.10
Additional context
I described a full info in my repo (it was easier for my) and I provided the full environment (docker image and docker-compose) to reproduce this bug, look at my repo: https://github.com/zerlok/python-bugs/tree/gino/enum-usage.
bug