es.davy.ai

Preguntas y respuestas de programación confiables

¿Tienes una pregunta?

Si tienes alguna pregunta, puedes hacerla a continuación o ingresar lo que estás buscando.

Cómo utilizar columnas JSON con SQLModel

Estoy tratando de definir una columna JSON a través de SQLModel:

from typing import Optional
from sqlmodel import Field, Session, SQLModel, create_engine, JSON


class Hero(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: Optional[int] = None
    meta: JSON

El código es de SQLModel, pero extendido por el atributo “meta”.

Usando el código anterior junto con el resto del código de ejemplo (configurando sqlite, agregando datos), obtengo el siguiente error:

RuntimeError: no se encontró un validador para <class 'sqlalchemy.sql.sqltypes.json'="">, consulta `arbitrary_types_allowed` en Config

Intenté extender el código de la siguiente manera:

class Hero(SQLModel, table=True):
    [...]
    meta: JSON

    @validator('meta')
    def validate_json(v):
        return v

    class Config:
        arbitrary_types_allowed = True

Pero esto da lugar a otro error:

sqlalchemy.exc.CompileError: (en la tabla 'hero', columna 'meta'): No se puede generar DDL para NullType (); ¿olvidaste especificar un tipo en esta columna?

Lo intenté usando solo SQLAlchemy y ha funcionado.

Entonces, ¿alguna idea de cómo puedo hacer la “conexión” entre SQLModel y SQLAlchemy para el campo JSON?

Actualización: También probé estableciéndolo como opcional y darle un valor predeterminado. Sin éxito (2º error nuevamente):

class Hero(SQLModel, table=True):
    [...]
    meta: Optional[JSON] = {}

    class Config:
        arbitrary_types_allowed = True

Pequeña pista: Aunque el JSON se importa desde SQLModel, finalmente se importa desde SQLAlchemy.sqltypes sin ningún cambio.

Tags:  , ,

Answer

  1. Avatar for davy.ai

    Parece que el problema está relacionado con el hecho de que SQLModel utiliza SQLAlchemy en su interior para operaciones de base de datos, pero el tipo de datos JSON no es completamente compatible con la autogeneración de DDL (Lenguaje de Definición de Datos) de SQLAlchemy.

    Para resolver este problema, puedes definir explícitamente el tipo de datos para la columna meta como JSONB, que es un tipo de datos JSON compatible con PostgreSQL y SQLite (asumiendo que estás utilizando una de estas bases de datos).

    Aquí tienes el código modificado que debería funcionar:

    from typing import Optional
    from sqlmodel import Field, Session, SQLModel, create_engine
    from sqlalchemy import JSON, Column
    from sqlalchemy.ext.declarative import declarative_base
    
    Base = declarative_base()
    
    class Hero(SQLModel, Base):
        __tablename__ = "hero"
        id: Optional[int] = Field(default=None, primary_key=True)
        name: str
        secret_name: str
        age: Optional[int] = None
        meta: Optional[dict] = {}
    
        meta_column = Column(JSON, default={}, nullable=True)
    
    sqlite_url = "sqlite:///database.db"
    engine = create_engine(sqlite_url)
    Base.metadata.create_all(engine)
    
    with Session(engine) as session:
        hero_1 = Hero(name="Daredevil", secret_name="Matthew Michael Murdock", age=32, meta={"powers": ["superhuman senses", "radar sense", "peak physical condition"]})
    
        session.add(hero_1)
        session.commit()
    

    En este código modificado, definimos meta_column como un objeto Column con un tipo de datos JSON, y luego especificamos que el atributo meta del modelo Hero es opcional y tiene un valor predeterminado de un diccionario vacío ({}).

    Al crear una nueva instancia de Hero, ahora podemos pasar un diccionario para el atributo meta y se almacenará como datos JSON en la columna meta_column de la base de datos.

    Ten en cuenta que también necesitamos usar la función declarative_base() de SQLAlchemy para crear una clase base de la cual hereda nuestro modelo Hero.

    ¡Espero que esto ayude! Avísame si tienes alguna pregunta adicional.

Comments are closed.