Skip to content

SQL: resolve legacy mode + deprecate mysql flavor (GH6900) #7077

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 1 commit into from
May 11, 2014
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
24 changes: 14 additions & 10 deletions doc/source/io.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3159,9 +3159,9 @@ your database.

.. versionadded:: 0.14.0


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

See also some :ref:`cookbook examples <cookbook.sql>` for some advanced strategies.
Expand Down Expand Up @@ -3335,24 +3335,28 @@ Engine connection examples
engine = create_engine('sqlite:////absolute/path/to/foo.db')


Legacy
~~~~~~
To use the sqlite support without SQLAlchemy, you can create connections like so:
Sqlite fallback
~~~~~~~~~~~~~~~

The use of sqlite is supported without using SQLAlchemy.
This mode requires a Python database adapter which respect the `Python
DB-API <http://www.python.org/dev/peps/pep-0249/>`__.

You can create connections like so:

.. code-block:: python

import sqlite3
from pandas.io import sql
cnx = sqlite3.connect(':memory:')

And then issue the following queries, remembering to also specify the flavor of SQL
you are using.
And then issue the following queries:

.. code-block:: python

data.to_sql('data', cnx, flavor='sqlite')
data.to_sql('data', cnx)

sql.read_sql("SELECT * FROM data", cnx, flavor='sqlite')
sql.read_sql("SELECT * FROM data", cnx)


.. _io.bigquery:
Expand Down
3 changes: 3 additions & 0 deletions doc/source/release.rst
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,9 @@ Deprecations
positional argument ``frame`` instead of ``data``. A ``FutureWarning`` is
raised if the old ``data`` argument is used by name. (:issue:`6956`)

- The support for the 'mysql' flavor when using DBAPI connection objects has been deprecated.
MySQL will be further supported with SQLAlchemy engines (:issue:`6900`).

Prior Version Deprecations/Changes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down
3 changes: 3 additions & 0 deletions doc/source/v0.14.0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,9 @@ Deprecations
returned if possible, otherwise a copy will be made. Previously the user could think that ``copy=False`` would
ALWAYS return a view. (:issue:`6894`)

- The support for the 'mysql' flavor when using DBAPI connection objects has been deprecated.
MySQL will be further supported with SQLAlchemy engines (:issue:`6900`).

.. _whatsnew_0140.enhancements:

Enhancements
Expand Down
71 changes: 35 additions & 36 deletions pandas/io/sql.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,32 +76,30 @@ def _parse_date_columns(data_frame, parse_dates):
return data_frame


def execute(sql, con, cur=None, params=None, flavor='sqlite'):
def execute(sql, con, cur=None, params=None):
"""
Execute the given SQL query using the provided connection object.

Parameters
----------
sql : string
Query to be executed
con : SQLAlchemy engine or DBAPI2 connection (legacy mode)
con : SQLAlchemy engine or sqlite3 DBAPI2 connection
Using SQLAlchemy makes it possible to use any DB supported by that
library.
If a DBAPI2 object, a supported SQL flavor must also be provided
If a DBAPI2 object, only sqlite3 is supported.
cur : depreciated, cursor is obtained from connection
params : list or tuple, optional
List of parameters to pass to execute method.
flavor : string "sqlite", "mysql"
Specifies the flavor of SQL to use.
Ignored when using SQLAlchemy engine. Required when using DBAPI2 connection.

Returns
-------
Results Iterable
"""
if cur is None:
pandas_sql = pandasSQL_builder(con, flavor=flavor)
pandas_sql = pandasSQL_builder(con)
else:
pandas_sql = pandasSQL_builder(cur, flavor=flavor, is_cursor=True)
pandas_sql = pandasSQL_builder(cur, is_cursor=True)
args = _convert_params(sql, params)
return pandas_sql.execute(*args)

Expand Down Expand Up @@ -235,7 +233,7 @@ def read_sql_table(table_name, con, meta=None, index_col=None,
table_name : string
Name of SQL table in database
con : SQLAlchemy engine
Legacy mode not supported
Sqlite DBAPI conncection mode not supported
meta : SQLAlchemy meta, optional
If omitted MetaData is reflected from engine
index_col : string, optional
Expand Down Expand Up @@ -277,8 +275,8 @@ def read_sql_table(table_name, con, meta=None, index_col=None,
raise ValueError("Table %s not found" % table_name, con)


def read_sql_query(sql, con, index_col=None, flavor='sqlite',
coerce_float=True, params=None, parse_dates=None):
def read_sql_query(sql, con, index_col=None, coerce_float=True, params=None,
parse_dates=None):
"""Read SQL query into a DataFrame.

Returns a DataFrame corresponding to the result set of the query
Expand All @@ -289,15 +287,12 @@ def read_sql_query(sql, con, index_col=None, flavor='sqlite',
----------
sql : string
SQL query to be executed
con : SQLAlchemy engine or DBAPI2 connection (legacy mode)
con : SQLAlchemy engine or sqlite3 DBAPI2 connection
Using SQLAlchemy makes it possible to use any DB supported by that
library.
If a DBAPI2 object is given, a supported SQL flavor must also be provided
If a DBAPI2 object, only sqlite3 is supported.
index_col : string, optional
column name to use for the returned DataFrame object.
flavor : string, {'sqlite', 'mysql'}
The flavor of SQL to use. Ignored when using
SQLAlchemy engine. Required when using DBAPI2 connection.
coerce_float : boolean, default True
Attempt to convert values to non-string, non-numeric objects (like
decimal.Decimal) to floating point, useful for SQL result sets
Expand All @@ -324,7 +319,7 @@ def read_sql_query(sql, con, index_col=None, flavor='sqlite',
read_sql

"""
pandas_sql = pandasSQL_builder(con, flavor=flavor)
pandas_sql = pandasSQL_builder(con)
return pandas_sql.read_sql(
sql, index_col=index_col, params=params, coerce_float=coerce_float,
parse_dates=parse_dates)
Expand All @@ -342,12 +337,13 @@ def read_sql(sql, con, index_col=None, flavor='sqlite', coerce_float=True,
con : SQLAlchemy engine or DBAPI2 connection (legacy mode)
Using SQLAlchemy makes it possible to use any DB supported by that
library.
If a DBAPI2 object is given, a supported SQL flavor must also be provided
If a DBAPI2 object, only sqlite3 is supported.
index_col : string, optional
column name to use for the returned DataFrame object.
flavor : string, {'sqlite', 'mysql'}
The flavor of SQL to use. Ignored when using
SQLAlchemy engine. Required when using DBAPI2 connection.
'mysql' is still supported, but will be removed in future versions.
coerce_float : boolean, default True
Attempt to convert values to non-string, non-numeric objects (like
decimal.Decimal) to floating point, useful for SQL result sets
Expand Down Expand Up @@ -417,13 +413,14 @@ def to_sql(frame, name, con, flavor='sqlite', if_exists='fail', index=True,
frame : DataFrame
name : string
Name of SQL table
con : SQLAlchemy engine or DBAPI2 connection (legacy mode)
con : SQLAlchemy engine or sqlite3 DBAPI2 connection
Using SQLAlchemy makes it possible to use any DB supported by that
library.
If a DBAPI2 object is given, a supported SQL flavor must also be provided
If a DBAPI2 object, only sqlite3 is supported.
flavor : {'sqlite', 'mysql'}, default 'sqlite'
The flavor of SQL to use. Ignored when using SQLAlchemy engine.
Required when using DBAPI2 connection.
'mysql' is still supported, but will be removed in future versions.
if_exists : {'fail', 'replace', 'append'}, default 'fail'
- fail: If table exists, do nothing.
- replace: If table exists, drop it, recreate it, and insert data.
Expand Down Expand Up @@ -458,13 +455,14 @@ def has_table(table_name, con, flavor='sqlite'):
----------
table_name: string
Name of SQL table
con: SQLAlchemy engine or DBAPI2 connection (legacy mode)
con: SQLAlchemy engine or sqlite3 DBAPI2 connection
Using SQLAlchemy makes it possible to use any DB supported by that
library.
If a DBAPI2 object is given, a supported SQL flavor name must also be provided
If a DBAPI2 object, only sqlite3 is supported.
flavor: {'sqlite', 'mysql'}, default 'sqlite'
The flavor of SQL to use. Ignored when using SQLAlchemy engine.
Required when using DBAPI2 connection.
'mysql' is still supported, but will be removed in future versions.

Returns
-------
Expand All @@ -476,6 +474,10 @@ def has_table(table_name, con, flavor='sqlite'):
table_exists = has_table


_MYSQL_WARNING = ("The 'mysql' flavor with DBAPI connection is deprecated "
"and will be removed in future versions. "
"MySQL will be further supported with SQLAlchemy engines.")

def pandasSQL_builder(con, flavor=None, meta=None, is_cursor=False):
"""
Convenience function to return the correct PandasSQL subclass based on the
Expand All @@ -489,21 +491,14 @@ def pandasSQL_builder(con, flavor=None, meta=None, is_cursor=False):
if isinstance(con, sqlalchemy.engine.Engine):
return PandasSQLAlchemy(con, meta=meta)
else:
warnings.warn("Not an SQLAlchemy engine, "
"attempting to use as legacy DBAPI connection")
if flavor is None:
raise ValueError(
"PandasSQL must be created with an SQLAlchemy engine "
"or a DBAPI2 connection and SQL flavor")
else:
return PandasSQLLegacy(con, flavor, is_cursor=is_cursor)
if flavor == 'mysql':
warnings.warn(_MYSQL_WARNING, FutureWarning)
return PandasSQLLegacy(con, flavor, is_cursor=is_cursor)

except ImportError:
warnings.warn("SQLAlchemy not installed, using legacy mode")
if flavor is None:
raise SQLAlchemyRequired
else:
return PandasSQLLegacy(con, flavor, is_cursor=is_cursor)
if flavor == 'mysql':
warnings.warn(_MYSQL_WARNING, FutureWarning)
return PandasSQLLegacy(con, flavor, is_cursor=is_cursor)


class PandasSQLTable(PandasObject):
Expand Down Expand Up @@ -893,7 +888,7 @@ def _create_sql_schema(self, frame, table_name):
}


_SAFE_NAMES_WARNING = ("The spaces in these column names will not be changed."
_SAFE_NAMES_WARNING = ("The spaces in these column names will not be changed. "
"In pandas versions < 0.14, spaces were converted to "
"underscores.")

Expand Down Expand Up @@ -991,6 +986,8 @@ class PandasSQLLegacy(PandasSQL):
def __init__(self, con, flavor, is_cursor=False):
self.is_cursor = is_cursor
self.con = con
if flavor is None:
flavor = 'sqlite'
if flavor not in ['sqlite', 'mysql']:
raise NotImplementedError
else:
Expand Down Expand Up @@ -1098,6 +1095,8 @@ def get_schema(frame, name, flavor='sqlite', keys=None, con=None):
"""

if con is None:
if flavor == 'mysql':
warnings.warn(_MYSQL_WARNING, FutureWarning)
return _get_schema_legacy(frame, name, flavor, keys)

pandas_sql = pandasSQL_builder(con=con, flavor=flavor)
Expand Down
Loading