evan54
evan54

Reputation: 3743

Are there any side effects with adding a method to a declerative class in sqlalchemy?

I had asked this question How to create instance of a table entry but not added in ponyorm? where I was asking how I can create an instance of the class defined as a ponyorm table representation without immediately adding it. By using sqlalchemy where an explicit add is needed on a session instance I think I succeeded by using the following code.

I first create a class called AddInstance which has an add method, and then inherit from this in all my table definitions. This seems to work (ie I can create an instance of the class and add it only if I want to relatively easily) but I'm not sure if there are any unintended side effects or this is very far from best practice.

from sqlalchemy import create_engine                                               
from sqlalchemy.ext.declarative import declarative_base                         
from sqlalchemy.orm import sessionmaker, relationship                           
from sqlalchemy import Column, String, Integer                                  


engine = create_engine('sqlite:///:memory:')                                    
Base = declarative_base()                                                       
Session = sessionmaker(bind=engine)                                                


class AddInstance:                                                                 

    def add(self):                                                              
        session = Session()                                                        
        session.add(self)                                                       
        session.commit()


class Pizza(Base, AddInstance):                                                    
    __tablename__ = 'pizzas'                                                       
    id = Column(Integer, primary_key=True)                                         
    name = Column(String(50))                                                      
    toppings = relationship('Topping', back_populates='name')                      


class Topping(Base, AddInstance):                                                  
    __tablename__ = 'fruits'                                                       
    id = Column(Integer, primary_key=True)                                         
    name = Column(String(50))                                                      
    pizzas = relationship('Pizza', back_populates='name')                          


Base.metadata.create_all(engine)                                                   

Upvotes: 0

Views: 1009

Answers (1)

Martijn Pieters
Martijn Pieters

Reputation: 1125078

There will be no side effects, SQLAlchemy explicitly supports adding methods. It doesn't matter if those methods are defined on a mixin class or directly on the class derived from Base.

Quoting from the SQLAlchemy Object Relational Tutorial documentation section:

Outside of what the mapping process does to our class, the class remains otherwise mostly a normal Python class, to which we can define any number of ordinary attributes and methods needed by our application.

(bold emphasis mine).

There are plenty of examples of code bases that do exactly the same. Flask-SQLAlchemy provides a Model base class to add a query attribute (set when the declarative base is created), which lets you do Topping.query(...) directly from a model class, for example.

Your add() method does have downside: it creates a new Session() instance just to add and commit your object. This keeps it outside of the normal session state management semantics and if you wanted to do anything else with your newly created object you'd have to merge it into an existing session.

The normal, best practice for code involvig SQLAlchemy objects is to create a session to manage a transaction, a set of operations that together must succeed or fail. That includes creating objects; in many real-world applications you'd want to avoid creating extra rows in a database when other operations that rely on those rows fail. Your .add() method unconditionally commits each object in a separate transaction. You may want to revisit this pattern.

Upvotes: 4

Related Questions