Skip to content

Commit cba041e

Browse files
Merge pull request #7077 from jorisvandenbossche/sql-legacy
SQL: resolve legacy mode + deprecate mysql flavor (GH6900)
2 parents aa31fd1 + 9311b04 commit cba041e

File tree

5 files changed

+76
-69
lines changed

5 files changed

+76
-69
lines changed

doc/source/io.rst

+14-10
Original file line numberDiff line numberDiff line change
@@ -3161,9 +3161,9 @@ your database.
31613161

31623162
.. versionadded:: 0.14.0
31633163

3164-
3165-
If SQLAlchemy is not installed a legacy fallback is provided for sqlite and mysql.
3166-
These legacy modes require Python database adapters which respect the `Python
3164+
If SQLAlchemy is not installed, a fallback is only provided for sqlite (and
3165+
for mysql for backwards compatibility, but this is deprecated).
3166+
This mode requires a Python database adapter which respect the `Python
31673167
DB-API <http://www.python.org/dev/peps/pep-0249/>`__.
31683168

31693169
See also some :ref:`cookbook examples <cookbook.sql>` for some advanced strategies.
@@ -3337,24 +3337,28 @@ Engine connection examples
33373337
engine = create_engine('sqlite:////absolute/path/to/foo.db')
33383338
33393339
3340-
Legacy
3341-
~~~~~~
3342-
To use the sqlite support without SQLAlchemy, you can create connections like so:
3340+
Sqlite fallback
3341+
~~~~~~~~~~~~~~~
3342+
3343+
The use of sqlite is supported without using SQLAlchemy.
3344+
This mode requires a Python database adapter which respect the `Python
3345+
DB-API <http://www.python.org/dev/peps/pep-0249/>`__.
3346+
3347+
You can create connections like so:
33433348

33443349
.. code-block:: python
33453350
33463351
import sqlite3
33473352
from pandas.io import sql
33483353
cnx = sqlite3.connect(':memory:')
33493354
3350-
And then issue the following queries, remembering to also specify the flavor of SQL
3351-
you are using.
3355+
And then issue the following queries:
33523356

33533357
.. code-block:: python
33543358
3355-
data.to_sql('data', cnx, flavor='sqlite')
3359+
data.to_sql('data', cnx)
33563360
3357-
sql.read_sql("SELECT * FROM data", cnx, flavor='sqlite')
3361+
sql.read_sql("SELECT * FROM data", cnx)
33583362
33593363
33603364
.. _io.bigquery:

doc/source/release.rst

+3
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,9 @@ Deprecations
247247
positional argument ``frame`` instead of ``data``. A ``FutureWarning`` is
248248
raised if the old ``data`` argument is used by name. (:issue:`6956`)
249249

250+
- The support for the 'mysql' flavor when using DBAPI connection objects has been deprecated.
251+
MySQL will be further supported with SQLAlchemy engines (:issue:`6900`).
252+
250253
Prior Version Deprecations/Changes
251254
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
252255

doc/source/v0.14.0.txt

+3
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,9 @@ Deprecations
478478
returned if possible, otherwise a copy will be made. Previously the user could think that ``copy=False`` would
479479
ALWAYS return a view. (:issue:`6894`)
480480

481+
- The support for the 'mysql' flavor when using DBAPI connection objects has been deprecated.
482+
MySQL will be further supported with SQLAlchemy engines (:issue:`6900`).
483+
481484
.. _whatsnew_0140.enhancements:
482485

483486
Enhancements

pandas/io/sql.py

+35-36
Original file line numberDiff line numberDiff line change
@@ -76,32 +76,30 @@ def _parse_date_columns(data_frame, parse_dates):
7676
return data_frame
7777

7878

79-
def execute(sql, con, cur=None, params=None, flavor='sqlite'):
79+
def execute(sql, con, cur=None, params=None):
8080
"""
8181
Execute the given SQL query using the provided connection object.
8282
8383
Parameters
8484
----------
8585
sql : string
8686
Query to be executed
87-
con : SQLAlchemy engine or DBAPI2 connection (legacy mode)
87+
con : SQLAlchemy engine or sqlite3 DBAPI2 connection
8888
Using SQLAlchemy makes it possible to use any DB supported by that
8989
library.
90-
If a DBAPI2 object, a supported SQL flavor must also be provided
90+
If a DBAPI2 object, only sqlite3 is supported.
9191
cur : depreciated, cursor is obtained from connection
9292
params : list or tuple, optional
9393
List of parameters to pass to execute method.
94-
flavor : string "sqlite", "mysql"
95-
Specifies the flavor of SQL to use.
96-
Ignored when using SQLAlchemy engine. Required when using DBAPI2 connection.
94+
9795
Returns
9896
-------
9997
Results Iterable
10098
"""
10199
if cur is None:
102-
pandas_sql = pandasSQL_builder(con, flavor=flavor)
100+
pandas_sql = pandasSQL_builder(con)
103101
else:
104-
pandas_sql = pandasSQL_builder(cur, flavor=flavor, is_cursor=True)
102+
pandas_sql = pandasSQL_builder(cur, is_cursor=True)
105103
args = _convert_params(sql, params)
106104
return pandas_sql.execute(*args)
107105

@@ -235,7 +233,7 @@ def read_sql_table(table_name, con, meta=None, index_col=None,
235233
table_name : string
236234
Name of SQL table in database
237235
con : SQLAlchemy engine
238-
Legacy mode not supported
236+
Sqlite DBAPI conncection mode not supported
239237
meta : SQLAlchemy meta, optional
240238
If omitted MetaData is reflected from engine
241239
index_col : string, optional
@@ -277,8 +275,8 @@ def read_sql_table(table_name, con, meta=None, index_col=None,
277275
raise ValueError("Table %s not found" % table_name, con)
278276

279277

280-
def read_sql_query(sql, con, index_col=None, flavor='sqlite',
281-
coerce_float=True, params=None, parse_dates=None):
278+
def read_sql_query(sql, con, index_col=None, coerce_float=True, params=None,
279+
parse_dates=None):
282280
"""Read SQL query into a DataFrame.
283281
284282
Returns a DataFrame corresponding to the result set of the query
@@ -289,15 +287,12 @@ def read_sql_query(sql, con, index_col=None, flavor='sqlite',
289287
----------
290288
sql : string
291289
SQL query to be executed
292-
con : SQLAlchemy engine or DBAPI2 connection (legacy mode)
290+
con : SQLAlchemy engine or sqlite3 DBAPI2 connection
293291
Using SQLAlchemy makes it possible to use any DB supported by that
294292
library.
295-
If a DBAPI2 object is given, a supported SQL flavor must also be provided
293+
If a DBAPI2 object, only sqlite3 is supported.
296294
index_col : string, optional
297295
column name to use for the returned DataFrame object.
298-
flavor : string, {'sqlite', 'mysql'}
299-
The flavor of SQL to use. Ignored when using
300-
SQLAlchemy engine. Required when using DBAPI2 connection.
301296
coerce_float : boolean, default True
302297
Attempt to convert values to non-string, non-numeric objects (like
303298
decimal.Decimal) to floating point, useful for SQL result sets
@@ -324,7 +319,7 @@ def read_sql_query(sql, con, index_col=None, flavor='sqlite',
324319
read_sql
325320
326321
"""
327-
pandas_sql = pandasSQL_builder(con, flavor=flavor)
322+
pandas_sql = pandasSQL_builder(con)
328323
return pandas_sql.read_sql(
329324
sql, index_col=index_col, params=params, coerce_float=coerce_float,
330325
parse_dates=parse_dates)
@@ -342,12 +337,13 @@ def read_sql(sql, con, index_col=None, flavor='sqlite', coerce_float=True,
342337
con : SQLAlchemy engine or DBAPI2 connection (legacy mode)
343338
Using SQLAlchemy makes it possible to use any DB supported by that
344339
library.
345-
If a DBAPI2 object is given, a supported SQL flavor must also be provided
340+
If a DBAPI2 object, only sqlite3 is supported.
346341
index_col : string, optional
347342
column name to use for the returned DataFrame object.
348343
flavor : string, {'sqlite', 'mysql'}
349344
The flavor of SQL to use. Ignored when using
350345
SQLAlchemy engine. Required when using DBAPI2 connection.
346+
'mysql' is still supported, but will be removed in future versions.
351347
coerce_float : boolean, default True
352348
Attempt to convert values to non-string, non-numeric objects (like
353349
decimal.Decimal) to floating point, useful for SQL result sets
@@ -417,13 +413,14 @@ def to_sql(frame, name, con, flavor='sqlite', if_exists='fail', index=True,
417413
frame : DataFrame
418414
name : string
419415
Name of SQL table
420-
con : SQLAlchemy engine or DBAPI2 connection (legacy mode)
416+
con : SQLAlchemy engine or sqlite3 DBAPI2 connection
421417
Using SQLAlchemy makes it possible to use any DB supported by that
422418
library.
423-
If a DBAPI2 object is given, a supported SQL flavor must also be provided
419+
If a DBAPI2 object, only sqlite3 is supported.
424420
flavor : {'sqlite', 'mysql'}, default 'sqlite'
425421
The flavor of SQL to use. Ignored when using SQLAlchemy engine.
426422
Required when using DBAPI2 connection.
423+
'mysql' is still supported, but will be removed in future versions.
427424
if_exists : {'fail', 'replace', 'append'}, default 'fail'
428425
- fail: If table exists, do nothing.
429426
- replace: If table exists, drop it, recreate it, and insert data.
@@ -458,13 +455,14 @@ def has_table(table_name, con, flavor='sqlite'):
458455
----------
459456
table_name: string
460457
Name of SQL table
461-
con: SQLAlchemy engine or DBAPI2 connection (legacy mode)
458+
con: SQLAlchemy engine or sqlite3 DBAPI2 connection
462459
Using SQLAlchemy makes it possible to use any DB supported by that
463460
library.
464-
If a DBAPI2 object is given, a supported SQL flavor name must also be provided
461+
If a DBAPI2 object, only sqlite3 is supported.
465462
flavor: {'sqlite', 'mysql'}, default 'sqlite'
466463
The flavor of SQL to use. Ignored when using SQLAlchemy engine.
467464
Required when using DBAPI2 connection.
465+
'mysql' is still supported, but will be removed in future versions.
468466
469467
Returns
470468
-------
@@ -476,6 +474,10 @@ def has_table(table_name, con, flavor='sqlite'):
476474
table_exists = has_table
477475

478476

477+
_MYSQL_WARNING = ("The 'mysql' flavor with DBAPI connection is deprecated "
478+
"and will be removed in future versions. "
479+
"MySQL will be further supported with SQLAlchemy engines.")
480+
479481
def pandasSQL_builder(con, flavor=None, meta=None, is_cursor=False):
480482
"""
481483
Convenience function to return the correct PandasSQL subclass based on the
@@ -489,21 +491,14 @@ def pandasSQL_builder(con, flavor=None, meta=None, is_cursor=False):
489491
if isinstance(con, sqlalchemy.engine.Engine):
490492
return PandasSQLAlchemy(con, meta=meta)
491493
else:
492-
warnings.warn("Not an SQLAlchemy engine, "
493-
"attempting to use as legacy DBAPI connection")
494-
if flavor is None:
495-
raise ValueError(
496-
"PandasSQL must be created with an SQLAlchemy engine "
497-
"or a DBAPI2 connection and SQL flavor")
498-
else:
499-
return PandasSQLLegacy(con, flavor, is_cursor=is_cursor)
494+
if flavor == 'mysql':
495+
warnings.warn(_MYSQL_WARNING, FutureWarning)
496+
return PandasSQLLegacy(con, flavor, is_cursor=is_cursor)
500497

501498
except ImportError:
502-
warnings.warn("SQLAlchemy not installed, using legacy mode")
503-
if flavor is None:
504-
raise SQLAlchemyRequired
505-
else:
506-
return PandasSQLLegacy(con, flavor, is_cursor=is_cursor)
499+
if flavor == 'mysql':
500+
warnings.warn(_MYSQL_WARNING, FutureWarning)
501+
return PandasSQLLegacy(con, flavor, is_cursor=is_cursor)
507502

508503

509504
class PandasSQLTable(PandasObject):
@@ -893,7 +888,7 @@ def _create_sql_schema(self, frame, table_name):
893888
}
894889

895890

896-
_SAFE_NAMES_WARNING = ("The spaces in these column names will not be changed."
891+
_SAFE_NAMES_WARNING = ("The spaces in these column names will not be changed. "
897892
"In pandas versions < 0.14, spaces were converted to "
898893
"underscores.")
899894

@@ -991,6 +986,8 @@ class PandasSQLLegacy(PandasSQL):
991986
def __init__(self, con, flavor, is_cursor=False):
992987
self.is_cursor = is_cursor
993988
self.con = con
989+
if flavor is None:
990+
flavor = 'sqlite'
994991
if flavor not in ['sqlite', 'mysql']:
995992
raise NotImplementedError
996993
else:
@@ -1098,6 +1095,8 @@ def get_schema(frame, name, flavor='sqlite', keys=None, con=None):
10981095
"""
10991096

11001097
if con is None:
1098+
if flavor == 'mysql':
1099+
warnings.warn(_MYSQL_WARNING, FutureWarning)
11011100
return _get_schema_legacy(frame, name, flavor, keys)
11021101

11031102
pandas_sql = pandasSQL_builder(con=con, flavor=flavor)

0 commit comments

Comments
 (0)