Skip to content

CLN: clean sqlalchemy import #42546

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Jul 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 25 additions & 71 deletions pandas/io/sql.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,37 +47,13 @@
from pandas.util.version import Version


class SQLAlchemyRequired(ImportError):
pass
Comment on lines -50 to -51
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused



class DatabaseError(IOError):
pass


# -----------------------------------------------------------------------------
# -- Helper functions

_SQLALCHEMY_INSTALLED: bool | None = None


def _is_sqlalchemy_connectable(con):
global _SQLALCHEMY_INSTALLED
if _SQLALCHEMY_INSTALLED is None:
try:
import sqlalchemy

_SQLALCHEMY_INSTALLED = True
except ImportError:
_SQLALCHEMY_INSTALLED = False

if _SQLALCHEMY_INSTALLED:
import sqlalchemy # noqa: F811

return isinstance(con, sqlalchemy.engine.Connectable)
else:
return False


def _gt14() -> bool:
"""
Expand Down Expand Up @@ -303,21 +279,14 @@ def read_sql_table(
--------
>>> pd.read_sql_table('table_name', 'postgres:///db_name') # doctest:+SKIP
"""
con = _engine_builder(con)
if not _is_sqlalchemy_connectable(con):
raise NotImplementedError(
"read_sql_table only supported for SQLAlchemy connectable."
)
import sqlalchemy
from sqlalchemy.schema import MetaData
from sqlalchemy.exc import InvalidRequestError

meta = MetaData(con, schema=schema)
pandas_sql = pandasSQL_builder(con, schema=schema)
try:
meta.reflect(only=[table_name], views=True)
except sqlalchemy.exc.InvalidRequestError as err:
pandas_sql.meta.reflect(only=[table_name], views=True)
except InvalidRequestError as err:
raise ValueError(f"Table {table_name} not found") from err

pandas_sql = SQLDatabase(con, meta=meta)
table = pandas_sql.read_table(
table_name,
index_col=index_col,
Expand Down Expand Up @@ -752,37 +721,29 @@ def has_table(table_name: str, con, schema: str | None = None):
table_exists = has_table


def _engine_builder(con):
"""
Returns a SQLAlchemy engine from a URI (if con is a string)
else it just return con without modifying it.
"""
global _SQLALCHEMY_INSTALLED
if isinstance(con, str):
try:
import sqlalchemy
except ImportError:
_SQLALCHEMY_INSTALLED = False
else:
con = sqlalchemy.create_engine(con)
return con

return con


def pandasSQL_builder(con, schema: str | None = None, meta=None):
def pandasSQL_builder(con, schema: str | None = None):
"""
Convenience function to return the correct PandasSQL subclass based on the
provided parameters.
"""
con = _engine_builder(con)
if _is_sqlalchemy_connectable(con):
return SQLDatabase(con, schema=schema, meta=meta)
elif isinstance(con, str):
raise ImportError("Using URI string without sqlalchemy installed.")
else:
import sqlite3

if isinstance(con, sqlite3.Connection) or con is None:
return SQLiteDatabase(con)

sqlalchemy = import_optional_dependency("sqlalchemy")

if isinstance(con, str):
con = sqlalchemy.create_engine(con)

if isinstance(con, sqlalchemy.engine.Connectable):
return SQLDatabase(con, schema=schema)

raise ValueError(
"pandas only support SQLAlchemy connectable(engine/connection) or"
"database string URI or sqlite3 DBAPI2 connection"
)


class SQLTable(PandasObject):
"""
Expand Down Expand Up @@ -1387,21 +1348,14 @@ class SQLDatabase(PandasSQL):
schema : string, default None
Name of SQL schema in database to write to (if database flavor
supports this). If None, use default schema (default).
meta : SQLAlchemy MetaData object, default None
If provided, this MetaData object is used instead of a newly
created. This allows to specify database flavor specific
arguments in the MetaData object.

"""

def __init__(self, engine, schema: str | None = None, meta=None):
self.connectable = engine
if not meta:
from sqlalchemy.schema import MetaData

meta = MetaData(self.connectable, schema=schema)
def __init__(self, engine, schema: str | None = None):
from sqlalchemy.schema import MetaData

self.meta = meta
self.connectable = engine
self.meta = MetaData(self.connectable, schema=schema)

@contextmanager
def run_transaction(self):
Expand Down
6 changes: 2 additions & 4 deletions pandas/tests/io/test_sql.py
Original file line number Diff line number Diff line change
Expand Up @@ -1372,8 +1372,7 @@ def test_sql_open_close(self):
@pytest.mark.skipif(SQLALCHEMY_INSTALLED, reason="SQLAlchemy is installed")
def test_con_string_import_error(self):
conn = "mysql://root@localhost/pandas"
msg = "Using URI string without sqlalchemy installed"
with pytest.raises(ImportError, match=msg):
with pytest.raises(ImportError, match="SQLAlchemy"):
sql.read_sql("SELECT * FROM iris", conn)

def test_read_sql_delegate(self):
Expand Down Expand Up @@ -2314,8 +2313,7 @@ def test_schema_support(self):
# because of transactional schemas
if isinstance(self.conn, sqlalchemy.engine.Engine):
engine2 = self.connect()
meta = sqlalchemy.MetaData(engine2, schema="other")
pdsql = sql.SQLDatabase(engine2, meta=meta)
pdsql = sql.SQLDatabase(engine2, schema="other")
pdsql.to_sql(df, "test_schema_other2", index=False)
pdsql.to_sql(df, "test_schema_other2", index=False, if_exists="replace")
pdsql.to_sql(df, "test_schema_other2", index=False, if_exists="append")
Expand Down