3

I am trying to insert the data to mysql server, While doing so when i try to add the data into the SQLalchemy sesion i am getting the error "Generator object has no attribute add"

db=get_db()
temp = schema.User(**filtered_dict)
insert_data=models.User(**temp.dict())
db = get_db()
db.add(insert_data)
db.commit()
db.refresh()

Session generator:

def get_db():
 
    db_session = sessionlocal()
    try:
        yield db_session
    finally:
        db_session.close()

session creation


from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

SQLALCHEMY_DATABASE_URL = "mysql+mysqlconnector://root:password@localhost:3305/line_api"

engine = create_engine(
    SQLALCHEMY_DATABASE_URL
)
sessionlocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)


Base = declarative_base()
2
  • @IljaEverilä i hopeits t=not the issue with the generator, Only issue is that my object "sessionlocal " has no attribute add Commented May 13, 2021 at 17:59
  • 2
    You do not call the get_db function directly, you usually use FastAPIs Depends() construct to insert it where it is required: def foo(db: SessionLocal = Depends(get_db)): - this will then be populated when the foo endpoint gets called. This is the reason for yield-ing the session, since it allows FastAPI to handle the dependency and clean it up when it is no longer in use. However, to do that, you have to use FastAPIs dependency injection methods and not call the method directly. Commented May 13, 2021 at 21:27

3 Answers 3

7
Simple Solution

I ran into a similar issue in my own FastAPI project with SQLAlchemy. The issue stems from the gathering of the db session from a yield keyword. yield returns a generator rather than returning the sqlalchemy db session as you're expecting. You can read more about the yield expression here: https://docs.python.org/3/reference/expressions.html#yieldexpr

To fix the code you need to call the internal __next__ function in some form on the generator object returned from get_db. You can do this a variety of ways (some may be more stylistically accurate than my suggestion).

temp = schema.User(**filtered_dict)
insert_data=models.User(**temp.dict())
db_gen = get_db()
db = next(db_gen)
db.add(insert_data)
db.commit()
db.refresh()

EDIT with var reference to generator Thanks to @DustinMichels in the comments.

Adding the next function call should fix the issue.


Using Async Generators

Since we are not prepending the function get_db with async we get a normal generator. However, if we add async to this function definition, we can no longer use __next__ as a solution. Making the function asynchronous would also help making sure connections are closed correctly (see comments).

If a developer decides to add the async keyword then you have a couple options:

FastAPI's Depends system does work perfectly with asynchronous generators and can call the function for you, populating a nice parameter with your connection.

Alternatively, you can call asend(None) on an asynchronous generator to get the first value of the asynchronous generator. (There are also better ways to do this as well like using an async for loop or __anext__ function call). More to read here: https://www.python.org/dev/peps/pep-0525/#implementation-details

async def get_db():
    db_session = sessionlocal()
    try:
        yield db_session
    finally:
        db_session.close()

# ...

temp = schema.User(**filtered_dict)
insert_data=models.User(**temp.dict())
db = get_db().asend(None)
db.add(insert_data)
db.commit()
db.refresh()
Sign up to request clarification or add additional context in comments.

5 Comments

@RandallShanePhD Is there a component of the question I didn't answer? I can edit it into the explanation.
Appreciate the callout @TFlexSoom - that's a solid answer!
I am feeling confused about the same bit of code. When will the finally block be executed, and close the db connection?
@DustinMichels, Thank you for the comment. That is a very good point that I will try to add in to the answer. From personal testing, it seems that the finally comes into play whenever the reference to the generator can be deleted. This does mean that it could trigger early with the way the code is currently written. I found that setting get_db() to a variable does help avoid closing the connection. Personally, I would recommend making the function async. This is what I did in my own implementation, and it works well when injected into the FastAPI dependency system.
Personal Testing Script if anyone is interested from the comment above. gist.github.com/TFlexSoom/485595a3d2fb7bbc5ac2e75196e46b31
0

What worked for me was simply calling the .begin() fx present in sessionmaker:

class MainDB:
    # create connection engine
    base_db_engine = create_engine(f'sqlite:///{get_project_root()}/db/base.db')

    # init tables
    Base = declarative_base()
    Base.metadata.create_all(base_db_engine)

class GenDBInterface:
    # create db session
    session: Session = sessionmaker(bind=MainDB.base_db_engine)

    def run(self):
        acc = Accounts(email='hello', password='hi', user_token='hello')
        with self.session.begin() as s:
            s.add(acc)

I'm not using this in a flask/FastAPI environment though. Might be different.

1 Comment

The session maker when run from outside the module scope then session.begin() works, like as in case of Flask-RESTFul where it is app.app_context()
0

you should use the function get_db in "with" sentence(then the class type is correct):

@contextmanager
def get_db():
    db = SessionLocal()
    try:
        yield db
        db.commit()  
    except Exception as e:
        db.rollback() 
        raise e
    finally:
        db.close()
with get_db() as db:
    try:
        db.add(user)
        db.flush()
        db.refresh(user)
    except:
        pass

2 Comments

If they are using FastAPI, they should not be doing this. They should use FastAPI's dependency injection mechanism instead.
@snakecharmerb yes, I will inject as session_factory in repository: python def _get_user_repository(): return UserRepository(get_db_session) def get_user_service(user_repo: UserRepository = Depends(_get_user_repository)): return UserService(user_repo)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.