-
Notifications
You must be signed in to change notification settings - Fork 330
Description
Description
sqlalchemy_utils.functions.create_database() does not correctly handle SQLAlchemy 2.x URL objects when passed as engine.url.
The documentation and examples explicitly show this usage pattern:
engine = create_engine('postgresql://postgres@localhost/name')
database_exists(engine.url) # => False
create_database(engine.url)
database_exists(engine.url) # => TrueSource:
| create_database(engine.url) |
However, with SQLAlchemy 2.x, passing engine.url (a structured URL object) causes create_database() to fail for certain backends (e.g. MySQL + PyMySQL), typically resulting in authentication or connection errors.
The same call works when a string URL is passed instead.
Actual Behavior
Calling create_database(engine.url) fails under SQLAlchemy 2.x with backend-specific errors (e.g. access denied, database creation failure).
sqlalchemy.exc.OperationalError: (pymysql.err.OperationalError) (1045, "Access denied for user 'user'@'localhost' (using password: YES)")
(Background on this error at: https://sqlalche.me/e/20/e3q8)Steps to reproduce
from sqlalchemy import create_engine
from sqlalchemy_utils import create_database
engine = create_engine("mysql+pymysql://user:xxxx@localhost:3306/dbname")
# Fails under SQLAlchemy 2.x
create_database(engine.url)
# Works
create_database("mysql+pymysql://user:xxxx@localhost:3306/dbname")
# Also works
create_database(engine.url.render_as_string(hide_password=False))Root Cause
SQLAlchemy 2.0 intentionally changed the semantics of URL objects:
engine.urlis now a structured, immutable object- Sensitive fields (e.g. passwords) are not preserved unless explicitly rendered
create_database() internally rebuilds an engine based on the provided URL and assumes round-trip equivalence, which is no longer valid in SQLAlchemy 2.x. When a URL object is passed directly, credentials or parameters may be dropped or altered.
Environment
sqlalchemy-utils: 0.42.1
SQLAlchemy: 2.0.44
PyMySQL: 1.1.2
Python: 3.12
Backend example: MySQL + PyMySQL