Skip to content

Commit 8acfad3

Browse files
gfyoungjorisvandenbossche
authored andcommitted
CLN: Removed the flavor='mysql' option and deprecate flavor in DataFrame.to_sql (#13611)
1 parent 4c9ae94 commit 8acfad3

File tree

4 files changed

+144
-298
lines changed

4 files changed

+144
-298
lines changed

doc/source/whatsnew/v0.19.0.txt

+2
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,7 @@ Deprecations
524524
- ``Categorical.reshape`` has been deprecated and will be removed in a subsequent release (:issue:`12882`)
525525
- ``Series.reshape`` has been deprecated and will be removed in a subsequent release (:issue:`12882`)
526526

527+
- ``DataFrame.to_sql()`` has deprecated the ``flavor`` parameter, as it is superfluous when SQLAlchemy is not installed (:issue:`13611`)
527528
- ``compact_ints`` and ``use_unsigned`` have been deprecated in ``pd.read_csv()`` and will be removed in a future version (:issue:`13320`)
528529
- ``buffer_lines`` has been deprecated in ``pd.read_csv()`` and will be removed in a future version (:issue:`13360`)
529530
- ``as_recarray`` has been deprecated in ``pd.read_csv()`` and will be removed in a future version (:issue:`13373`)
@@ -541,6 +542,7 @@ Removal of prior version deprecations/changes
541542
- ``DataFrame.to_dict()`` has dropped the ``outtype`` parameter in favor of ``orient`` (:issue:`13627`, :issue:`8486`)
542543
- ``pd.Categorical`` has dropped setting of the ``ordered`` attribute directly in favor of the ``set_ordered`` method (:issue:`13671`)
543544
- ``pd.Categorical`` has dropped the ``levels`` attribute in favour of ``categories`` (:issue:`8376`)
545+
- ``DataFrame.to_sql()`` has dropped the ``mysql`` option for the ``flavor`` parameter (:issue:`13611`)
544546

545547
- Removal of the legacy time rules (offset aliases), deprecated since 0.17.0 (this has been alias since 0.8.0) (:issue:`13590`)
546548

pandas/core/generic.py

+6-7
Original file line numberDiff line numberDiff line change
@@ -1144,7 +1144,7 @@ def to_msgpack(self, path_or_buf=None, encoding='utf-8', **kwargs):
11441144
return packers.to_msgpack(path_or_buf, self, encoding=encoding,
11451145
**kwargs)
11461146

1147-
def to_sql(self, name, con, flavor='sqlite', schema=None, if_exists='fail',
1147+
def to_sql(self, name, con, flavor=None, schema=None, if_exists='fail',
11481148
index=True, index_label=None, chunksize=None, dtype=None):
11491149
"""
11501150
Write records stored in a DataFrame to a SQL database.
@@ -1155,12 +1155,11 @@ def to_sql(self, name, con, flavor='sqlite', schema=None, if_exists='fail',
11551155
Name of SQL table
11561156
con : SQLAlchemy engine or DBAPI2 connection (legacy mode)
11571157
Using SQLAlchemy makes it possible to use any DB supported by that
1158-
library.
1159-
If a DBAPI2 object, only sqlite3 is supported.
1160-
flavor : {'sqlite', 'mysql'}, default 'sqlite'
1161-
The flavor of SQL to use. Ignored when using SQLAlchemy engine.
1162-
'mysql' is deprecated and will be removed in future versions, but
1163-
it will be further supported through SQLAlchemy engines.
1158+
library. If a DBAPI2 object, only sqlite3 is supported.
1159+
flavor : 'sqlite', default None
1160+
DEPRECATED: this parameter will be removed in a future version,
1161+
as 'sqlite' is the only supported option if SQLAlchemy is not
1162+
installed.
11641163
schema : string, default None
11651164
Specify the schema (if database flavor supports this). If None, use
11661165
default schema.

pandas/io/sql.py

+53-112
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,24 @@ class DatabaseError(IOError):
4141
_SQLALCHEMY_INSTALLED = None
4242

4343

44+
def _validate_flavor_parameter(flavor):
45+
"""
46+
Checks whether a database 'flavor' was specified.
47+
If not None, produces FutureWarning if 'sqlite' and
48+
raises a ValueError if anything else.
49+
"""
50+
if flavor is not None:
51+
if flavor == 'sqlite':
52+
warnings.warn("the 'flavor' parameter is deprecated "
53+
"and will be removed in a future version, "
54+
"as 'sqlite' is the only supported option "
55+
"when SQLAlchemy is not installed.",
56+
FutureWarning, stacklevel=2)
57+
else:
58+
raise ValueError("database flavor {flavor} is not "
59+
"supported".format(flavor=flavor))
60+
61+
4462
def _is_sqlalchemy_connectable(con):
4563
global _SQLALCHEMY_INSTALLED
4664
if _SQLALCHEMY_INSTALLED is None:
@@ -517,7 +535,7 @@ def read_sql(sql, con, index_col=None, coerce_float=True, params=None,
517535
chunksize=chunksize)
518536

519537

520-
def to_sql(frame, name, con, flavor='sqlite', schema=None, if_exists='fail',
538+
def to_sql(frame, name, con, flavor=None, schema=None, if_exists='fail',
521539
index=True, index_label=None, chunksize=None, dtype=None):
522540
"""
523541
Write records stored in a DataFrame to a SQL database.
@@ -532,10 +550,8 @@ def to_sql(frame, name, con, flavor='sqlite', schema=None, if_exists='fail',
532550
Using SQLAlchemy makes it possible to use any DB supported by that
533551
library.
534552
If a DBAPI2 object, only sqlite3 is supported.
535-
flavor : {'sqlite', 'mysql'}, default 'sqlite'
536-
The flavor of SQL to use. Ignored when using SQLAlchemy connectable.
537-
'mysql' is deprecated and will be removed in future versions, but it
538-
will be further supported through SQLAlchemy connectables.
553+
flavor : 'sqlite', default None
554+
DEPRECATED: this parameter will be removed in a future version
539555
schema : string, default None
540556
Name of SQL schema in database to write to (if database flavor
541557
supports this). If None, use default schema (default).
@@ -573,7 +589,7 @@ def to_sql(frame, name, con, flavor='sqlite', schema=None, if_exists='fail',
573589
chunksize=chunksize, dtype=dtype)
574590

575591

576-
def has_table(table_name, con, flavor='sqlite', schema=None):
592+
def has_table(table_name, con, flavor=None, schema=None):
577593
"""
578594
Check if DataBase has named table.
579595
@@ -585,10 +601,8 @@ def has_table(table_name, con, flavor='sqlite', schema=None):
585601
Using SQLAlchemy makes it possible to use any DB supported by that
586602
library.
587603
If a DBAPI2 object, only sqlite3 is supported.
588-
flavor: {'sqlite', 'mysql'}, default 'sqlite'
589-
The flavor of SQL to use. Ignored when using SQLAlchemy connectable.
590-
'mysql' is deprecated and will be removed in future versions, but it
591-
will be further supported through SQLAlchemy connectables.
604+
flavor : 'sqlite', default None
605+
DEPRECATED: this parameter will be removed in a future version
592606
schema : string, default None
593607
Name of SQL schema in database to write to (if database flavor supports
594608
this). If None, use default schema (default).
@@ -603,12 +617,6 @@ def has_table(table_name, con, flavor='sqlite', schema=None):
603617
table_exists = has_table
604618

605619

606-
_MYSQL_WARNING = ("The 'mysql' flavor with DBAPI connection is deprecated "
607-
"and will be removed in future versions. "
608-
"MySQL will be further supported with SQLAlchemy "
609-
"connectables.")
610-
611-
612620
def _engine_builder(con):
613621
"""
614622
Returns a SQLAlchemy engine from a URI (if con is a string)
@@ -632,15 +640,15 @@ def pandasSQL_builder(con, flavor=None, schema=None, meta=None,
632640
Convenience function to return the correct PandasSQL subclass based on the
633641
provided parameters
634642
"""
643+
_validate_flavor_parameter(flavor)
644+
635645
# When support for DBAPI connections is removed,
636646
# is_cursor should not be necessary.
637647
con = _engine_builder(con)
638648
if _is_sqlalchemy_connectable(con):
639649
return SQLDatabase(con, schema=schema, meta=meta)
640650
else:
641-
if flavor == 'mysql':
642-
warnings.warn(_MYSQL_WARNING, FutureWarning, stacklevel=3)
643-
return SQLiteDatabase(con, flavor, is_cursor=is_cursor)
651+
return SQLiteDatabase(con, is_cursor=is_cursor)
644652

645653

646654
class SQLTable(PandasObject):
@@ -1035,11 +1043,11 @@ class PandasSQL(PandasObject):
10351043

10361044
def read_sql(self, *args, **kwargs):
10371045
raise ValueError("PandasSQL must be created with an SQLAlchemy "
1038-
"connectable or connection+sql flavor")
1046+
"connectable or sqlite connection")
10391047

10401048
def to_sql(self, *args, **kwargs):
10411049
raise ValueError("PandasSQL must be created with an SQLAlchemy "
1042-
"connectable or connection+sql flavor")
1050+
"connectable or sqlite connection")
10431051

10441052

10451053
class SQLDatabase(PandasSQL):
@@ -1308,38 +1316,16 @@ def _create_sql_schema(self, frame, table_name, keys=None, dtype=None):
13081316

13091317

13101318
# ---- SQL without SQLAlchemy ---
1311-
# Flavour specific sql strings and handler class for access to DBs without
1312-
# SQLAlchemy installed
1313-
# SQL type convertions for each DB
1319+
# sqlite-specific sql strings and handler class
1320+
# dictionary used for readability purposes
13141321
_SQL_TYPES = {
1315-
'string': {
1316-
'mysql': 'VARCHAR (63)',
1317-
'sqlite': 'TEXT',
1318-
},
1319-
'floating': {
1320-
'mysql': 'DOUBLE',
1321-
'sqlite': 'REAL',
1322-
},
1323-
'integer': {
1324-
'mysql': 'BIGINT',
1325-
'sqlite': 'INTEGER',
1326-
},
1327-
'datetime': {
1328-
'mysql': 'DATETIME',
1329-
'sqlite': 'TIMESTAMP',
1330-
},
1331-
'date': {
1332-
'mysql': 'DATE',
1333-
'sqlite': 'DATE',
1334-
},
1335-
'time': {
1336-
'mysql': 'TIME',
1337-
'sqlite': 'TIME',
1338-
},
1339-
'boolean': {
1340-
'mysql': 'BOOLEAN',
1341-
'sqlite': 'INTEGER',
1342-
}
1322+
'string': 'TEXT',
1323+
'floating': 'REAL',
1324+
'integer': 'INTEGER',
1325+
'datetime': 'TIMESTAMP',
1326+
'date': 'DATE',
1327+
'time': 'TIME',
1328+
'boolean': 'INTEGER',
13431329
}
13441330

13451331

@@ -1351,22 +1337,6 @@ def _get_unicode_name(name):
13511337
return uname
13521338

13531339

1354-
def _get_valid_mysql_name(name):
1355-
# Filter for unquoted identifiers
1356-
# See http://dev.mysql.com/doc/refman/5.0/en/identifiers.html
1357-
uname = _get_unicode_name(name)
1358-
if not len(uname):
1359-
raise ValueError("Empty table or column name specified")
1360-
1361-
basere = r'[0-9,a-z,A-Z$_]'
1362-
for c in uname:
1363-
if not re.match(basere, c):
1364-
if not (0x80 < ord(c) < 0xFFFF):
1365-
raise ValueError("Invalid MySQL identifier '%s'" % uname)
1366-
1367-
return '`' + uname + '`'
1368-
1369-
13701340
def _get_valid_sqlite_name(name):
13711341
# See http://stackoverflow.com/questions/6514274/how-do-you-escape-strings\
13721342
# -for-sqlite-table-column-names-in-python
@@ -1385,19 +1355,6 @@ def _get_valid_sqlite_name(name):
13851355
return '"' + uname.replace('"', '""') + '"'
13861356

13871357

1388-
# SQL enquote and wildcard symbols
1389-
_SQL_WILDCARD = {
1390-
'mysql': '%s',
1391-
'sqlite': '?'
1392-
}
1393-
1394-
# Validate and return escaped identifier
1395-
_SQL_GET_IDENTIFIER = {
1396-
'mysql': _get_valid_mysql_name,
1397-
'sqlite': _get_valid_sqlite_name,
1398-
}
1399-
1400-
14011358
_SAFE_NAMES_WARNING = ("The spaces in these column names will not be changed. "
14021359
"In pandas versions < 0.14, spaces were converted to "
14031360
"underscores.")
@@ -1428,9 +1385,8 @@ def _execute_create(self):
14281385

14291386
def insert_statement(self):
14301387
names = list(map(text_type, self.frame.columns))
1431-
flv = self.pd_sql.flavor
1432-
wld = _SQL_WILDCARD[flv] # wildcard char
1433-
escape = _SQL_GET_IDENTIFIER[flv]
1388+
wld = '?' # wildcard char
1389+
escape = _get_valid_sqlite_name
14341390

14351391
if self.index is not None:
14361392
[names.insert(0, idx) for idx in self.index[::-1]]
@@ -1460,8 +1416,7 @@ def _create_table_setup(self):
14601416
if any(map(pat.search, column_names)):
14611417
warnings.warn(_SAFE_NAMES_WARNING, stacklevel=6)
14621418

1463-
flv = self.pd_sql.flavor
1464-
escape = _SQL_GET_IDENTIFIER[flv]
1419+
escape = _get_valid_sqlite_name
14651420

14661421
create_tbl_stmts = [escape(cname) + ' ' + ctype
14671422
for cname, ctype, _ in column_names_and_types]
@@ -1514,33 +1469,25 @@ def _sql_type_name(self, col):
15141469
if col_type not in _SQL_TYPES:
15151470
col_type = "string"
15161471

1517-
return _SQL_TYPES[col_type][self.pd_sql.flavor]
1472+
return _SQL_TYPES[col_type]
15181473

15191474

15201475
class SQLiteDatabase(PandasSQL):
15211476
"""
15221477
Version of SQLDatabase to support sqlite connections (fallback without
15231478
sqlalchemy). This should only be used internally.
15241479
1525-
For now still supports `flavor` argument to deal with 'mysql' database
1526-
for backwards compatibility, but this will be removed in future versions.
1527-
15281480
Parameters
15291481
----------
15301482
con : sqlite connection object
15311483
15321484
"""
15331485

1534-
def __init__(self, con, flavor, is_cursor=False):
1486+
def __init__(self, con, flavor=None, is_cursor=False):
1487+
_validate_flavor_parameter(flavor)
1488+
15351489
self.is_cursor = is_cursor
15361490
self.con = con
1537-
if flavor is None:
1538-
flavor = 'sqlite'
1539-
if flavor not in ['sqlite', 'mysql']:
1540-
raise NotImplementedError("flavors other than SQLite and MySQL "
1541-
"are not supported")
1542-
else:
1543-
self.flavor = flavor
15441491

15451492
@contextmanager
15461493
def run_transaction(self):
@@ -1665,24 +1612,20 @@ def to_sql(self, frame, name, if_exists='fail', index=True,
16651612

16661613
def has_table(self, name, schema=None):
16671614
# TODO(wesm): unused?
1668-
# escape = _SQL_GET_IDENTIFIER[self.flavor]
1615+
# escape = _get_valid_sqlite_name
16691616
# esc_name = escape(name)
16701617

1671-
wld = _SQL_WILDCARD[self.flavor]
1672-
flavor_map = {
1673-
'sqlite': ("SELECT name FROM sqlite_master "
1674-
"WHERE type='table' AND name=%s;") % wld,
1675-
'mysql': "SHOW TABLES LIKE %s" % wld}
1676-
query = flavor_map.get(self.flavor)
1618+
wld = '?'
1619+
query = ("SELECT name FROM sqlite_master "
1620+
"WHERE type='table' AND name=%s;") % wld
16771621

16781622
return len(self.execute(query, [name, ]).fetchall()) > 0
16791623

16801624
def get_table(self, table_name, schema=None):
16811625
return None # not supported in fallback mode
16821626

16831627
def drop_table(self, name, schema=None):
1684-
escape = _SQL_GET_IDENTIFIER[self.flavor]
1685-
drop_sql = "DROP TABLE %s" % escape(name)
1628+
drop_sql = "DROP TABLE %s" % _get_valid_sqlite_name(name)
16861629
self.execute(drop_sql)
16871630

16881631
def _create_sql_schema(self, frame, table_name, keys=None, dtype=None):
@@ -1691,7 +1634,7 @@ def _create_sql_schema(self, frame, table_name, keys=None, dtype=None):
16911634
return str(table.sql_schema())
16921635

16931636

1694-
def get_schema(frame, name, flavor='sqlite', keys=None, con=None, dtype=None):
1637+
def get_schema(frame, name, flavor=None, keys=None, con=None, dtype=None):
16951638
"""
16961639
Get the SQL db table schema for the given frame.
16971640
@@ -1700,16 +1643,14 @@ def get_schema(frame, name, flavor='sqlite', keys=None, con=None, dtype=None):
17001643
frame : DataFrame
17011644
name : string
17021645
name of SQL table
1703-
flavor : {'sqlite', 'mysql'}, default 'sqlite'
1704-
The flavor of SQL to use. Ignored when using SQLAlchemy connectable.
1705-
'mysql' is deprecated and will be removed in future versions, but it
1706-
will be further supported through SQLAlchemy engines.
17071646
keys : string or sequence, default: None
17081647
columns to use a primary key
17091648
con: an open SQL database connection object or a SQLAlchemy connectable
17101649
Using SQLAlchemy makes it possible to use any DB supported by that
17111650
library, default: None
17121651
If a DBAPI2 object, only sqlite3 is supported.
1652+
flavor : 'sqlite', default None
1653+
DEPRECATED: this parameter will be removed in a future version
17131654
dtype : dict of column name to SQL type, default None
17141655
Optional specifying the datatype for columns. The SQL type should
17151656
be a SQLAlchemy type, or a string for sqlite3 fallback connection.

0 commit comments

Comments
 (0)