164

Using SQLAlchemy, an Engine object is created like this:

from sqlalchemy import create_engine
engine = create_engine("postgresql://localhost/mydb")

Accessing engine fails if the database specified in the argument to create_engine (in this case, mydb) does not exist. Is it possible to tell SQLAlchemy to create a new database if the specified database doesn't exist?

msouth
  • 832
  • 11
  • 21
Anand Chitipothu
  • 4,167
  • 4
  • 24
  • 26
  • 2
    Create a new database or just tables? I've not come across many ORMs that actually create databases. – Noufal Ibrahim Jun 28 '11 at 13:33
  • 5
    I did find [this](http://www.mail-archive.com/sqlalchemy@googlegroups.com/msg05520.html) – Noufal Ibrahim Jun 28 '11 at 13:35
  • 4
    Helpful: http://sqlalchemy-utils.readthedocs.org/en/latest/database_helpers.html – Artjom B. Jun 21 '15 at 16:42
  • 2
    ^^^ sqlalchemy-utils is the way to go. See the answer below: https://stackoverflow.com/a/30971098/3559330 – mattyb Mar 04 '21 at 23:24
  • FYI According to zzzeek, you typically create a new database outside of your codebase, or alternatively in Alembic: https://github.com/sqlalchemy/alembic/issues/614#issuecomment-547509136 – jrc Jul 28 '21 at 08:52

6 Answers6

197

SQLAlchemy-Utils provides custom data types and various utility functions for SQLAlchemy. You can install the most recent official version using pip:

pip install sqlalchemy-utils

The database helpers include a create_database function:

from sqlalchemy import create_engine
from sqlalchemy_utils import database_exists, create_database

engine = create_engine("postgres://localhost/mydb")
if not database_exists(engine.url):
    create_database(engine.url)

print(database_exists(engine.url))
Brian Burns
  • 20,575
  • 8
  • 83
  • 77
buhtz
  • 10,774
  • 18
  • 76
  • 149
  • 3
    I get this error when trying this exact codeblock: `psycopg2.OperationalError: fe_sendauth: no password supplied`. When using `"postgres://test:abc123@localhost:5432/test"` I get `psycopg2.OperationalError: FATAL: password authentication failed for user "test"` – Guus Feb 07 '19 at 15:23
  • Sorry for the spam, but I tried changing the port to 9000 and now I get this: `"postgres://test:abc123@localhost:9000/test"` ```psycopg2.OperationalError: server closed the connection unexpectedly This probably means the server terminated abnormally before or while processing the request. ``` – Guus Feb 07 '19 at 15:34
  • `psql -U postgres`, and then you can create a password with `\password`. Then you create the engine via `engine = create_engine("postgres://postgres:yourpassword@localhost/mydb")` – Vladimir Vargas Nov 22 '20 at 22:14
  • 2
    I had no idea this package existed - thank you so much! – mattyb Mar 04 '21 at 23:23
  • 1
    this didn't work for me on python 3.8.2, sqlalchemy 1.3.2, sqlalchemy-utils 0.37. a recent similar comment was made for the `database_exists()` function [here](https://stackoverflow.com/a/28181968/) – arturomp Apr 22 '21 at 20:06
127

On postgres, three databases are normally present by default. If you are able to connect as a superuser (eg, the postgres role), then you can connect to the postgres or template1 databases. The default pg_hba.conf permits only the unix user named postgres to use the postgres role, so the simplest thing is to just become that user. At any rate, create an engine as usual with a user that has the permissions to create a database:

>>> engine = sqlalchemy.create_engine("postgres://postgres@/postgres")

You cannot use engine.execute() however, because postgres does not allow you to create databases inside transactions, and sqlalchemy always tries to run queries in a transaction. To get around this, get the underlying connection from the engine:

>>> conn = engine.connect()

But the connection will still be inside a transaction, so you have to end the open transaction with a commit:

>>> conn.execute("commit")

And you can then proceed to create the database using the proper PostgreSQL command for it.

>>> conn.execute("create database test")
>>> conn.close()
SingleNegationElimination
  • 151,563
  • 33
  • 264
  • 304
  • 3
    This worked well for me. As a side note, when I did `conn.execute('drop database DBWithCaps')` I had problems with it not recognizing the caps. `conn.execute('drop database "DBWithCaps"')` (with the quotes) worked fine. – KobeJohn Oct 06 '13 at 14:49
  • I know that PostgreSQL expects all entities in lower case, unless quoted. So if you created a field using MyColumn some DBs will take it as mycolumn. In other words, not sure how you created your table, but if it was created using quotes, it *will* case-sensitive, so when you access it in an SQL statement you'll need the quotes as well. – Guy Aug 03 '16 at 12:00
  • If you use sql alchemy with clickhouse remove ```conn.execute("commit")``` – ethicnology Oct 31 '22 at 10:15
15

It's possible to avoid manual transaction management while creating database by providing isolation_level='AUTOCOMMIT' to create_engine function:

import sqlalchemy

with sqlalchemy.create_engine(
    'postgresql:///postgres',
    isolation_level='AUTOCOMMIT'
).connect() as connection:
    connection.execute('CREATE DATABASE my_database')

Also if you are not sure that database doesn't exist there is a way to ignore database creation error due to existence by suppressing sqlalchemy.exc.ProgrammingError exception:

import contextlib
import sqlalchemy.exc

with contextlib.suppress(sqlalchemy.exc.ProgrammingError):
    # creating database as above
renskiy
  • 1,330
  • 1
  • 13
  • 12
  • It seems that you can't connect to a progres server without specifying a database, so you'll probably want to connect to the default "postgres" database to execute the db creation commands, otherwise it will try to connect to the default "user" database and complain if it doesn't exist. – Acorn May 13 '15 at 16:21
8

Extending the accepted answer using with yields:

from sqlalchemy import create_engine
engine = create_engine("postgresql://localhost")

NEW_DB_NAME = 'database_name'

with engine.connect() as conn:
    conn.execute("commit")
    # Do not substitute user-supplied database names here.
    conn.execute(f"CREATE DATABASE {NEW_DB_NAME}")
ATH
  • 666
  • 6
  • 13
  • You forgot the line `conn.execute("commit")` before creating the database – AugBar May 29 '21 at 20:13
  • I don't think we need a commit, as `CREATE DATABASE` issues an implicit commit. Reference for MySQL: https://dev.mysql.com/doc/refman/8.0/en/implicit-commit.html Reference for other RDMS: https://www.datasunrise.com/blog/professional-info/how-popular-rdbmss-deal-with-ddl-commands-in-transactions/ – ATH Jun 14 '21 at 12:52
  • The default isolation level is `READ COMMITED` when invoking `create_connection`, so you do need to commit before executing the query - or explicitly pass the argument `isolation_level=AUTOCOMMIT` – AugBar Jun 15 '21 at 11:57
  • Thanks for clarifying, added your suggestion in the answer:) – ATH Jun 15 '21 at 20:26
  • isn't the f-string an unsafe practice? – baggiponte Oct 12 '22 at 18:50
  • the f-string is a safe practice, as the variable is hardcoded. It would be a different story if the variable was from user input, that would allow a SQL injection – fuomag9 Feb 03 '23 at 18:16
3

Please note that I couldn't get the above suggestions with database_exists because whenever I check if the database exists using if not database_exists(engine.url): I get this error:

InterfaceError('(pyodbc.InterfaceError) (\'28000\', u\'[28000] [Microsoft][SQL Server Native Client 11.0][SQL Server]Login failed for user \\'myUser\\'. (18456) (SQLDriverConnect); [28000] [Microsoft][SQL Server Native Client 11.0][SQL Server]Cannot open database "MY_DATABASE" requested by the login. The login failed. (4060); [28000] [Microsoft][SQL Server Native Client 11.0][SQL Server]Login failed for user \\'myUser\\'. (18456); [28000] [Microsoft][SQL Server Native Client 11.0][SQL Server]Cannot open database "MY_DATABASE" requested by the login. The login failed. (4060)\')',)

Also contextlib/suppress was not working and I'm not using postgres so I ended up doing this to ignore the exception if the database happens to already exist with SQL Server:

import logging
import sqlalchemy

logging.basicConfig(filename='app.log', format='%(asctime)s-%(levelname)s-%(message)s', level=logging.DEBUG)
engine = create_engine('mssql+pyodbc://myUser:mypwd@localhost:1234/MY_DATABASE?driver=SQL+Server+Native+Client+11.0?trusted_connection=yes', isolation_level = "AUTOCOMMIT")

try: 
    engine.execute('CREATE DATABASE ' + a_database_name)
except Exception as db_exc:
    logging.exception("Exception creating database: " + str(db_exc))  
user8128167
  • 6,929
  • 6
  • 66
  • 79
1

If someone like me don't want to take whole sqlalchemy_utils to your project just for database creation, you can use script like this. I've come with it, based on SingleNegationElimination's answer. I'm using pydantic here (it's FastAPI project) and my imported settings for reference, but you can easily change this:

from sqlalchemy import create_engine
from sqlalchemy.exc import OperationalError
from pydantic import PostgresDsn

from src.conf import settings


def build_db_connection_url(custom_db: Optional[str] = None):
    db_name = f"/{settings.POSTGRES_DB or ''}" if custom_db is None else "/" + custom_db
    return PostgresDsn.build(
        scheme='postgresql+psycopg2',
        user=settings.POSTGRES_USER,
        password=settings.POSTGRES_PASSWORD,
        host=settings.POSTGRES_HOST,
        path=db_name,
    )


def create_database(db_name: str):
    try:
        eng = create_engine(build_db_connection_url(custom_db=db_name))
        conn = eng.connect()
        conn.close()
    except OperationalError as exc:
        if "does not exist" in exc.__str__():
            eng = create_engine(build_db_connection_url(custom_db="postgres"))
            conn = eng.connect()
            conn.execute("commit")
            conn.execute(f"create database {db_name}")
            conn.close()
            print(f"Database {db_name} created")
        else:
            raise exc
    eng.dispose()

create_database("test_database")
Dmitry
  • 470
  • 6
  • 9