87

I want to delete a table using SQLAlchemy.

Since I am testing over and over again, I want to delete the table my_users so that I can start from scratch every single time.

So far I am using SQLAlchemy to execute raw SQL through the engine.execute() method:

sql = text('DROP TABLE IF EXISTS my_users;')
result = engine.execute(sql)

However, I wonder if there is some standard way to do so. The only one I could find is drop_all(), but it deletes all the structure, not only one specific table:

Base.metadata.drop_all(engine)   # all tables are deleted

For example, given this very basic example. It consists on a SQLite infrastructure with a single table my_users in which I add some content.

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

engine = create_engine('sqlite://', echo=False)
Base = declarative_base()

class User(Base):
    __tablename__ = "my_users"

    id = Column(Integer, primary_key=True)
    name = Column(String)

    def __init__(self, name):
        self.name = name

# Create all the tables in the database which are
# defined by Base's subclasses such as User
Base.metadata.create_all(engine)

# Construct a sessionmaker factory object
session = sessionmaker()

# Bind the sessionmaker to engine
session.configure(bind=engine)

# Generate a session to work with
s = session()

# Add some content
s.add(User('myname'))
s.commit()

# Fetch the data
print(s.query(User).filter(User.name == 'myname').one().name)

For this specific case, drop_all() would work, but it won't be convenient from the moment I start having more than one table and I want to keep the other ones.

fedorqui
  • 275,237
  • 103
  • 548
  • 598
  • Note: Must surround sql query in `text(...)` for it to work. If this is omitted, then SQLAlchemy does not work, and it does not give any feedback as to why it did not work. – Contango Mar 23 '19 at 12:24

5 Answers5

92

Just call drop() against the table object. From the docs:

Issue a DROP statement for this Table, using the given Connectable for connectivity.

In your case it should be:

User.__table__.drop()

If you get an exception like:

sqlalchemy.exc.UnboundExecutionError: Table object 'my_users' is not bound to an Engine or Connection. Execution can not proceed without a database to execute against

You need to pass the engine:

User.__table__.drop(engine)
fedorqui
  • 275,237
  • 103
  • 548
  • 598
daveoncode
  • 18,900
  • 15
  • 104
  • 159
  • 1
    This only works if the table exists, it will give an error if you delete the whole .db file and want to create it again. I made a follow up question to this, please take a look if you want: http://stackoverflow.com/questions/39493174/how-to-delete-multiple-tables-in-sqlalchemy – NumesSanguis Sep 14 '16 at 14:36
  • This can't be used in a transaction. I want to rollback a transaction if tables are dropped, but other queries like creating them again raise exceptions. With this I can't do that. – Karina Klinkevičiūtė Feb 27 '19 at 16:21
  • 1
    @KarinaKlinkevičiūtė You can check if the table does (not) exist first; e.g. `engine = db.engine; if not engine.dialect.has_table(engine, ModelClass.__tablename__): ModelClass.__table__.create(engine)` – code_dredd Aug 06 '19 at 16:56
  • 1
    Accessing `db = SQLAlchemy(app)` and then doing `User.__table__.drop(db.engine)` worked for me – Sameer Kumar Jan 16 '20 at 14:30
  • Is there not a public API for this, rather then using a private `__table__` attribute? – ajrlewis Apr 19 '21 at 19:42
  • If you find that your call to `User.__table__.drop()` seems to hang/freeze (particularly in Postgres), see solution here: https://github.com/sqlalchemy/sqlalchemy/issues/4685 – Brendano257 Jul 18 '21 at 19:54
  • @code_dredd I have used a similar syntax in my code but the problem still persists. Getting error : "The argument passed to Dialect.has_table() should be a , got . Additionally, the Dialect.has_table() method is for internal dialect use only; please use ``inspect(some_engine).has_table(>)`` for public API use." Your inputs will be highly appreciated. Thanks. – sLau4 Jan 11 '22 at 13:44
  • this is not compatible with sqlalchemy 2 – Galuoises Mar 13 '23 at 20:12
37

Alternative to calling cls.__table__.drop(your_engine), you can try this:

Base.metadata.drop_all(bind=your_engine, tables=[User.__table__])

This method as well as the create_all() method accept an optional argument tables, which takes an iterator of sqlalchemy.sql.schema.Table instances.

You can control which tables are to be created or dropped in this way.

Nathaniel Jones
  • 939
  • 1
  • 14
  • 25
WeiHao
  • 732
  • 1
  • 7
  • 9
  • 3
    To me, this answer is clearer in semantics. Creating or dropping tables is more related to database level, so it is better to deal with Base instead of mapping class. Maybe SqlAlchemy does quite the same thing in the background. – WeiHao Apr 04 '18 at 06:34
21

For the special case when you don't have access to the table class and just need to delete the table by table name then use this code

import logging
from sqlalchemy import MetaData
from sqlalchemy import create_engine
from sqlalchemy.engine.url import URL
from sqlalchemy.ext.declarative import declarative_base

DATABASE = {
   'drivername': 'sqlite',
   # 'host': 'localhost',
   # 'port': '5432',
   # 'username': 'YOUR_USERNAME',
   # 'password': 'YOUR_PASSWORD',
   'database': '/path/to/your_db.sqlite'
}

def drop_table(table_name):
   engine = create_engine(URL(**DATABASE))
   base = declarative_base()
   metadata = MetaData(engine, reflect=True)
   table = metadata.tables.get(table_name)
   if table is not None:
       logging.info(f'Deleting {table_name} table')
       base.metadata.drop_all(engine, [table], checkfirst=True)

drop_table('users')
Levon
  • 10,408
  • 4
  • 47
  • 42
  • 3
    note [a new answer](https://stackoverflow.com/a/66376565/1983854) was posted mentioning the deprecation of `MetaData(engine, reflect=True)`. – fedorqui Feb 26 '21 at 07:09
13

How to delete a table by name

Here's an update of @Levon's answer, since MetaData(engine, reflect=True) is now deprecated. It is useful if you don't have access to the table class or want to delete a table by its table name.

from sqlalchemy import MetaData
from sqlalchemy import create_engine
from sqlalchemy.engine.url import URL
from sqlalchemy.ext.declarative import declarative_base

DATABASE = {
   'drivername': 'sqlite',
   # 'host': 'localhost',
   # 'port': '5432',
   # 'username': 'YOUR_USERNAME',
   # 'password': 'YOUR_PASSWORD',
   'database': '/path/to/your_db.sqlite'
}

engine = create_engine(URL(**DATABASE))

def drop_table(table_name, engine=engine):
    Base = declarative_base()
    metadata = MetaData()
    metadata.reflect(bind=engine)
    table = metadata.tables[table_name]
    if table is not None:
        Base.metadata.drop_all(engine, [table], checkfirst=True)

drop_table('users')

How to delete a table using the table class (preferred)

Otherwise, you may prefer to use cls.__table__.drop(engine) and cls.__table__.create(engine) instead, e.g.

User.__table__.drop(engine)
User.__table__.create(engine)
ATH
  • 666
  • 6
  • 13
9

Below is example code you can execute in iPython to test the creation and deletion of a table on Postgres

from sqlalchemy import * # imports all needed modules from sqlalchemy

engine = create_engine('postgresql://python:python@127.0.0.1/production') # connection properties stored

metadata = MetaData() # stores the 'production' database's metadata

users = Table('users', metadata,
Column('user_id', Integer),
Column('first_name', String(150)),
Column('last_name', String(150)),
Column('email', String(255)),
schema='python') # defines the 'users' table structure in the 'python' schema of our connection to the 'production' db

users.create(engine) # creates the users table

users.drop(engine) # drops the users table

You can also preview my article on Wordpress with this same example and screenshots: oscarvalles.wordpress.com (search for SQL Alchemy).

oscarvalles
  • 91
  • 1
  • 1