Skip to content

Commit aaf360b

Browse files
fangchenliCGe0516
authored andcommitted
CLN: clean sqlalchemy import (pandas-dev#42546)
1 parent 61d9733 commit aaf360b

File tree

2 files changed

+27
-75
lines changed

2 files changed

+27
-75
lines changed

pandas/io/sql.py

+25-71
Original file line numberDiff line numberDiff line change
@@ -47,37 +47,13 @@
4747
from pandas.util.version import Version
4848

4949

50-
class SQLAlchemyRequired(ImportError):
51-
pass
52-
53-
5450
class DatabaseError(IOError):
5551
pass
5652

5753

5854
# -----------------------------------------------------------------------------
5955
# -- Helper functions
6056

61-
_SQLALCHEMY_INSTALLED: bool | None = None
62-
63-
64-
def _is_sqlalchemy_connectable(con):
65-
global _SQLALCHEMY_INSTALLED
66-
if _SQLALCHEMY_INSTALLED is None:
67-
try:
68-
import sqlalchemy
69-
70-
_SQLALCHEMY_INSTALLED = True
71-
except ImportError:
72-
_SQLALCHEMY_INSTALLED = False
73-
74-
if _SQLALCHEMY_INSTALLED:
75-
import sqlalchemy # noqa: F811
76-
77-
return isinstance(con, sqlalchemy.engine.Connectable)
78-
else:
79-
return False
80-
8157

8258
def _gt14() -> bool:
8359
"""
@@ -303,21 +279,14 @@ def read_sql_table(
303279
--------
304280
>>> pd.read_sql_table('table_name', 'postgres:///db_name') # doctest:+SKIP
305281
"""
306-
con = _engine_builder(con)
307-
if not _is_sqlalchemy_connectable(con):
308-
raise NotImplementedError(
309-
"read_sql_table only supported for SQLAlchemy connectable."
310-
)
311-
import sqlalchemy
312-
from sqlalchemy.schema import MetaData
282+
from sqlalchemy.exc import InvalidRequestError
313283

314-
meta = MetaData(con, schema=schema)
284+
pandas_sql = pandasSQL_builder(con, schema=schema)
315285
try:
316-
meta.reflect(only=[table_name], views=True)
317-
except sqlalchemy.exc.InvalidRequestError as err:
286+
pandas_sql.meta.reflect(only=[table_name], views=True)
287+
except InvalidRequestError as err:
318288
raise ValueError(f"Table {table_name} not found") from err
319289

320-
pandas_sql = SQLDatabase(con, meta=meta)
321290
table = pandas_sql.read_table(
322291
table_name,
323292
index_col=index_col,
@@ -752,37 +721,29 @@ def has_table(table_name: str, con, schema: str | None = None):
752721
table_exists = has_table
753722

754723

755-
def _engine_builder(con):
756-
"""
757-
Returns a SQLAlchemy engine from a URI (if con is a string)
758-
else it just return con without modifying it.
759-
"""
760-
global _SQLALCHEMY_INSTALLED
761-
if isinstance(con, str):
762-
try:
763-
import sqlalchemy
764-
except ImportError:
765-
_SQLALCHEMY_INSTALLED = False
766-
else:
767-
con = sqlalchemy.create_engine(con)
768-
return con
769-
770-
return con
771-
772-
773-
def pandasSQL_builder(con, schema: str | None = None, meta=None):
724+
def pandasSQL_builder(con, schema: str | None = None):
774725
"""
775726
Convenience function to return the correct PandasSQL subclass based on the
776727
provided parameters.
777728
"""
778-
con = _engine_builder(con)
779-
if _is_sqlalchemy_connectable(con):
780-
return SQLDatabase(con, schema=schema, meta=meta)
781-
elif isinstance(con, str):
782-
raise ImportError("Using URI string without sqlalchemy installed.")
783-
else:
729+
import sqlite3
730+
731+
if isinstance(con, sqlite3.Connection) or con is None:
784732
return SQLiteDatabase(con)
785733

734+
sqlalchemy = import_optional_dependency("sqlalchemy")
735+
736+
if isinstance(con, str):
737+
con = sqlalchemy.create_engine(con)
738+
739+
if isinstance(con, sqlalchemy.engine.Connectable):
740+
return SQLDatabase(con, schema=schema)
741+
742+
raise ValueError(
743+
"pandas only support SQLAlchemy connectable(engine/connection) or"
744+
"database string URI or sqlite3 DBAPI2 connection"
745+
)
746+
786747

787748
class SQLTable(PandasObject):
788749
"""
@@ -1387,21 +1348,14 @@ class SQLDatabase(PandasSQL):
13871348
schema : string, default None
13881349
Name of SQL schema in database to write to (if database flavor
13891350
supports this). If None, use default schema (default).
1390-
meta : SQLAlchemy MetaData object, default None
1391-
If provided, this MetaData object is used instead of a newly
1392-
created. This allows to specify database flavor specific
1393-
arguments in the MetaData object.
13941351
13951352
"""
13961353

1397-
def __init__(self, engine, schema: str | None = None, meta=None):
1398-
self.connectable = engine
1399-
if not meta:
1400-
from sqlalchemy.schema import MetaData
1401-
1402-
meta = MetaData(self.connectable, schema=schema)
1354+
def __init__(self, engine, schema: str | None = None):
1355+
from sqlalchemy.schema import MetaData
14031356

1404-
self.meta = meta
1357+
self.connectable = engine
1358+
self.meta = MetaData(self.connectable, schema=schema)
14051359

14061360
@contextmanager
14071361
def run_transaction(self):

pandas/tests/io/test_sql.py

+2-4
Original file line numberDiff line numberDiff line change
@@ -1372,8 +1372,7 @@ def test_sql_open_close(self):
13721372
@pytest.mark.skipif(SQLALCHEMY_INSTALLED, reason="SQLAlchemy is installed")
13731373
def test_con_string_import_error(self):
13741374
conn = "mysql://root@localhost/pandas"
1375-
msg = "Using URI string without sqlalchemy installed"
1376-
with pytest.raises(ImportError, match=msg):
1375+
with pytest.raises(ImportError, match="SQLAlchemy"):
13771376
sql.read_sql("SELECT * FROM iris", conn)
13781377

13791378
def test_read_sql_delegate(self):
@@ -2314,8 +2313,7 @@ def test_schema_support(self):
23142313
# because of transactional schemas
23152314
if isinstance(self.conn, sqlalchemy.engine.Engine):
23162315
engine2 = self.connect()
2317-
meta = sqlalchemy.MetaData(engine2, schema="other")
2318-
pdsql = sql.SQLDatabase(engine2, meta=meta)
2316+
pdsql = sql.SQLDatabase(engine2, schema="other")
23192317
pdsql.to_sql(df, "test_schema_other2", index=False)
23202318
pdsql.to_sql(df, "test_schema_other2", index=False, if_exists="replace")
23212319
pdsql.to_sql(df, "test_schema_other2", index=False, if_exists="append")

0 commit comments

Comments
 (0)