tutorial instalar python django sqlalchemy

python - instalar - ¿Tiene SQLAlchemy un equivalente de get_or_create de Django?



sqlalchemy python install (8)

Quiero obtener un objeto de la base de datos si ya existe (según los parámetros proporcionados) o crearlo si no existe.

get_or_create (o source ) de Django hace esto. ¿Hay un atajo equivalente en SQLAlchemy?

Actualmente lo estoy escribiendo explícitamente así:

def get_or_create_instrument(session, serial_number): instrument = session.query(Instrument).filter_by(serial_number=serial_number).first() if instrument: return instrument else: instrument = Instrument(serial_number) session.add(instrument) return instrument


Dependiendo del nivel de aislamiento que adoptó, ninguna de las soluciones anteriores funcionaría. La mejor solución que he encontrado es un RAW SQL en la siguiente forma:

INSERT INTO table(f1, f2, unique_f3) SELECT ''v1'', ''v2'', ''v3'' WHERE NOT EXISTS (SELECT 1 FROM table WHERE f3 = ''v3'')

Esto es seguro para las transacciones, cualquiera que sea el nivel de aislamiento y el grado de paralelismo.

Cuidado: para que sea eficiente, sería aconsejable tener un ÍNDICE para la columna única.


El semántico más cercano es probablemente:

def get_or_create(model, **kwargs): """SqlAlchemy implementation of Django''s get_or_create. """ session = Session() instance = session.query(model).filter_by(**kwargs).first() if instance: return instance, False else: instance = model(**kwargs) session.add(instance) session.commit() return instance, True

No estoy seguro de qué tan kosher es confiar en una Session definida globalmente en sqlalchemy, pero la versión de Django no tiene conexión, así que ...

La tupla devuelta contiene la instancia y un valor booleano que indica si la instancia se creó (es decir, es False si leemos la instancia desde la base de datos).

get_or_create de Django se usa a menudo para asegurarse de que los datos globales estén disponibles, por lo que me comprometo lo antes posible.


Esa es básicamente la forma de hacerlo, no hay un acceso directo disponible AFAIK.

Podrías generalizarlo por supuesto:

def get_or_create(session, model, defaults=None, **kwargs): instance = session.query(model).filter_by(**kwargs).first() if instance: return instance, False else: params = dict((k, v) for k, v in kwargs.iteritems() if not isinstance(v, ClauseElement)) params.update(defaults or {}) instance = model(**params) session.add(instance) return instance, True


He estado jugando con este problema y he terminado con una solución bastante robusta:

def get_one_or_create(session, model, create_method='''', create_method_kwargs=None, **kwargs): try: return session.query(model).filter_by(**kwargs).one(), False except NoResultFound: kwargs.update(create_method_kwargs or {}) created = getattr(model, create_method, model)(**kwargs) try: session.add(created) session.flush() return created, True except IntegrityError: session.rollback() return session.query(model).filter_by(**kwargs).one(), False

Acabo de escribir una entrada de blog bastante amplia sobre todos los detalles, pero algunas ideas bastante claras de por qué usé esto.

  1. Se desempaqueta en una tupla que le dice si el objeto existió o no. Esto a menudo puede ser útil en su flujo de trabajo.

  2. La función ofrece la posibilidad de trabajar con funciones de creador decoradas con @classmethod (y atributos específicos para ellas).

  3. La solución protege contra las Condiciones de Carrera cuando tiene más de un proceso conectado al almacén de datos.

EDITAR: He cambiado session.commit() a session.flush() como se explica en esta publicación del blog . Tenga en cuenta que estas decisiones son específicas para el almacén de datos utilizado (Postgres en este caso).

EDITAR 2: He actualizado utilizando {} como un valor predeterminado en la función, ya que es típico de Python Gotcha. Gracias por el comentario , Nigel! Si tiene curiosidad por esta pregunta, consulte esta pregunta de y esta publicación del blog .


He simplificado un poco @Kevin. Solución para evitar envolver toda la función en una sentencia if / else . De esta manera solo hay una return , que me parece más limpia:

def get_or_create(session, model, **kwargs): instance = session.query(model).filter_by(**kwargs).first() if not instance: instance = model(**kwargs) session.add(instance) return instance


Siguiendo la solución de @WoLpH, este es el código que funcionó para mí (versión simple):

def get_or_create(session, model, **kwargs): instance = session.query(model).filter_by(**kwargs).first() if instance: return instance else: instance = model(**kwargs) session.add(instance) session.commit() return instance

Con esto, soy capaz de obtener o crear cualquier objeto de mi modelo.

Supongamos que mi objeto modelo es:

class Country(Base): __tablename__ = ''countries'' id = Column(Integer, primary_key=True) name = Column(String, unique=True)

Para obtener o crear mi objeto escribo:

myCountry = get_or_create(session, Country, name=countryName)


Una versión modificada de la excelente answer de Erik.

def get_one_or_create(session, model, create_method='''', create_method_kwargs=None, **kwargs): try: return session.query(model).filter_by(**kwargs).one(), True except NoResultFound: kwargs.update(create_method_kwargs or {}) try: with session.begin_nested(): created = getattr(model, create_method, model)(**kwargs) session.add(created) return created, False except IntegrityError: return session.query(model).filter_by(**kwargs).one(), True

  • Use una transacción anidada para revertir solo la adición del nuevo artículo en lugar de revertir todo (consulte esta answer para usar transacciones anidadas con SQLite)
  • Mueve create_method . Si el objeto creado tiene relaciones y se le asignan miembros a través de esas relaciones, se agrega automáticamente a la sesión. Por ejemplo, crear un book , que tenga user_id y user como relación correspondiente, luego hacer book.user=<user object> dentro de create_method agregará book a la sesión. Esto significa que create_method debe estar dentro para beneficiarse de un posible retroceso. Tenga en cuenta que begin_nested desencadena automáticamente una descarga.

Tenga en cuenta que si utiliza MySQL, el nivel de aislamiento de la transacción debe establecerse en READ COMMITTED lugar de REPEATABLE READ para que esto funcione. get_or_create de Django (y here ) usa la misma estratagema, vea también la documentation Django.


Esta receta de SQLALchemy hace el trabajo agradable y elegante.

Lo primero que debe hacer es definir una función a la que se le da una sesión para trabajar, y asociar un diccionario con la sesión () que realiza un seguimiento de las claves únicas actuales.

def _unique(session, cls, hashfunc, queryfunc, constructor, arg, kw): cache = getattr(session, ''_unique_cache'', None) if cache is None: session._unique_cache = cache = {} key = (cls, hashfunc(*arg, **kw)) if key in cache: return cache[key] else: with session.no_autoflush: q = session.query(cls) q = queryfunc(q, *arg, **kw) obj = q.first() if not obj: obj = constructor(*arg, **kw) session.add(obj) cache[key] = obj return obj

Un ejemplo de la utilización de esta función sería en una mezcla:

class UniqueMixin(object): @classmethod def unique_hash(cls, *arg, **kw): raise NotImplementedError() @classmethod def unique_filter(cls, query, *arg, **kw): raise NotImplementedError() @classmethod def as_unique(cls, session, *arg, **kw): return _unique( session, cls, cls.unique_hash, cls.unique_filter, cls, arg, kw )

Y finalmente creando el único modelo get_or_create:

from sqlalchemy import Column, Integer, String, create_engine from sqlalchemy.orm import sessionmaker from sqlalchemy.ext.declarative import declarative_base Base = declarative_base() engine = create_engine(''sqlite://'', echo=True) Session = sessionmaker(bind=engine) class Widget(UniqueMixin, Base): __tablename__ = ''widget'' id = Column(Integer, primary_key=True) name = Column(String, unique=True, nullable=False) @classmethod def unique_hash(cls, name): return name @classmethod def unique_filter(cls, query, name): return query.filter(Widget.name == name) Base.metadata.create_all(engine) session = Session() w1, w2, w3 = Widget.as_unique(session, name=''w1''), / Widget.as_unique(session, name=''w2''), / Widget.as_unique(session, name=''w3'') w1b = Widget.as_unique(session, name=''w1'') assert w1 is w1b assert w2 is not w3 assert w2 is not w1 session.commit()

La receta profundiza en la idea y proporciona diferentes enfoques, pero he utilizado esta con gran éxito.