77import numpy as np
88import traceback
99
10+ import sqlite3
11+ import warnings
12+
1013from pandas .core .datetools import format as date_format
1114from pandas .core .api import DataFrame , isnull
15+ from pandas .io import sql_legacy
1216
1317#------------------------------------------------------------------------------
1418# Helper execution function
@@ -132,8 +136,85 @@ def uquery(sql, con=None, cur=None, retry=True, params=None):
132136 return uquery (sql , con , retry = False )
133137 return result
134138
139+ class SQLAlchemyRequired (Exception ):
140+ pass
141+
142+ class LegacyMySQLConnection (Exception ):
143+ pass
135144
136- def read_frame (sql , con , index_col = None , coerce_float = True , params = None ):
145+ def get_connection (con , dialect , driver , username , password ,
146+ host , port , database ):
147+ if isinstance (con , basestring ):
148+ try :
149+ import sqlalchemy
150+ return _alchemy_connect_sqlite (con )
151+ except :
152+ return sqlite3 .connect (con )
153+ if isinstance (con , sqlite3 .Connection ):
154+ return con
155+ try :
156+ import MySQLdb
157+ except ImportError :
158+ # If we don't have MySQLdb, this can't be a MySQLdb connection.
159+ pass
160+ else :
161+ if isinstance (con , MySQLdb .connection ):
162+ raise LegacyMySQLConnection
163+ # If we reach here, SQLAlchemy will be needed.
164+ try :
165+ import sqlalchemy
166+ except ImportError :
167+ raise SQLAlchemyRequired
168+ if isinstance (con , sqlalchemy .engine .Engine ):
169+ return con .connect ()
170+ if isinstance (con , sqlalchemy .engine .Connection ):
171+ return con
172+ if con is None :
173+ url_params = (dialect , driver , username , \
174+ password , host , port , database )
175+ url = _build_url (* url_params )
176+ engine = sqlalchemy .create_engine (url )
177+ return engine .connect ()
178+ if hasattr (con , 'cursor' ) and callable (con .cursor ):
179+ # This looks like some Connection object from a driver module.
180+ raise NotImplementedError , \
181+ """To ensure robust support of varied SQL dialects, pandas
182+ only supports database connections from SQLAlchemy. (Legacy
183+ support for MySQLdb connections are available but buggy.)"""
184+ else :
185+ raise ValueError , \
186+ """con must be a string, a Connection to a sqlite Database,
187+ or a SQLAlchemy Connection or Engine object."""
188+
189+
190+ def _alchemy_connect_sqlite (path ):
191+ if path == ':memory:' :
192+ return create_engine ('sqlite://' ).connect ()
193+ else :
194+ return create_engine ('sqlite:///%s' % path ).connect ()
195+
196+ def _build_url (dialect , driver , username , password , host , port , database ):
197+ # Create an Engine and from that a Connection.
198+ # We use a string instead of sqlalchemy.engine.url.URL because
199+ # we do not necessarily know the driver; we know the dialect.
200+ required_params = [dialect , username , password , host , database ]
201+ for p in required_params :
202+ if not isinstance (p , basestring ):
203+ raise ValueError , \
204+ "Insufficient information to connect to a database;" \
205+ "see docstring."
206+ url = dialect
207+ if driver is not None :
208+ url += "+%s" % driver
209+ url += "://%s:%s@%s" % (username , password , host )
210+ if port is not None :
211+ url += ":%d" % port
212+ url += "/%s" % database
213+ return url
214+
215+ def read_sql (sql , con = None , index_col = None , flavor = None , driver = None ,
216+ username = None , password = None , host = None , port = None ,
217+ database = None , coerce_float = True , params = None ):
137218 """
138219 Returns a DataFrame corresponding to the result set of the query
139220 string.
@@ -145,32 +226,52 @@ def read_frame(sql, con, index_col=None, coerce_float=True, params=None):
145226 ----------
146227 sql: string
147228 SQL query to be executed
148- con: DB connection object, optional
229+ con : Connection object, SQLAlchemy Engine object, a filepath string
230+ (sqlite only) or the string ':memory:' (sqlite only). Alternatively,
231+ specify a user, passwd, host, and db below.
149232 index_col: string, optional
150233 column name to use for the returned DataFrame object.
234+ flavor : string specifying the flavor of SQL to use
235+ driver : string specifying SQL driver (e.g., MySQLdb), optional
236+ username: username for database authentication
237+ only needed if a Connection, Engine, or filepath are not given
238+ password: password for database authentication
239+ only needed if a Connection, Engine, or filepath are not given
240+ host: host for database connection
241+ only needed if a Connection, Engine, or filepath are not given
242+ database: database name
243+ only needed if a Connection, Engine, or filepath are not given
151244 coerce_float : boolean, default True
152245 Attempt to convert values to non-string, non-numeric objects (like
153246 decimal.Decimal) to floating point, useful for SQL result sets
154247 params: list or tuple, optional
155248 List of parameters to pass to execute method.
156249 """
157- cur = execute (sql , con , params = params )
158- rows = _safe_fetch (cur )
159- columns = [col_desc [0 ] for col_desc in cur .description ]
160-
161- cur .close ()
162- con .commit ()
163-
164- result = DataFrame .from_records (rows , columns = columns ,
165- coerce_float = coerce_float )
250+ dialect = flavor
251+ try :
252+ connection = get_connection (con , dialect , driver , username , password ,
253+ host , port , database )
254+ except LegacyMySQLConnection :
255+ warnings .warn ("For more robust support, connect using " \
256+ "SQLAlchemy. See documentation." )
257+ return sql_legacy .read_frame (sql , con , index_col , coerce_float , params )
258+
259+ if params is None :
260+ params = []
261+ cursor = connection .execute (sql , * params )
262+ result = _safe_fetch (cursor )
263+ columns = [col_desc [0 ] for col_desc in cursor .description ]
264+ cursor .close ()
265+
266+ result = DataFrame .from_records (result , columns = columns )
166267
167268 if index_col is not None :
168269 result = result .set_index (index_col )
169270
170271 return result
171272
172- frame_query = read_frame
173- read_sql = read_frame
273+ frame_query = read_sql
274+ read_frame = read_sql
174275
175276def write_frame (frame , name , con , flavor = 'sqlite' , if_exists = 'fail' , ** kwargs ):
176277 """
0 commit comments